diff --git a/Changes b/Changes index 2e3871e046..6a9014f8aa 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,15 @@ 10.x.x.x (relative to 10.6.x.x) ======== +Features +-------- + +- IECore : Added Rampff, RampfColor3f, RampfColor4f and corresponding Ramp*Data classes, for representing user facing shader ramp parameters. + +Breaking Changes +---------------- +- IECoreScene::ShaderNetworkAlgo : Switch support for Spline parameters to support for Ramp parameters. 10.6.x.x (relative to 10.6.1.0) ======== diff --git a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp index 3219f221bf..1e37785aee 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp @@ -65,7 +65,12 @@ namespace { const pxr::TfToken g_blindDataToken( "cortex:blindData" ); -pxr::TfToken g_legacyAdapterLabelToken( IECoreScene::ShaderNetworkAlgo::componentConnectionAdapterLabel().string() ); + +// Hardcoded to match an old name used by Cortex when writing USD with OSL version earlier than 1.10 ( +// ie. a pre-2021 version of gafferDependencies ). We no longer expose this in the API, but I'm not sure +// if we're ready to drop support for loading these old files. +IECore::InternedString g_legacyAdapterLabelString( "cortex_autoAdapter" ); +pxr::TfToken g_legacyAdapterLabelToken( g_legacyAdapterLabelString.string() ); std::pair shaderIdAndType( const pxr::UsdShadeConnectableAPI &connectable ) { @@ -267,7 +272,7 @@ IECore::InternedString readShaderNetworkWalk( const pxr::SdfPath &anchorPath, co pxr::VtValue metadataValue; if( usdShader.GetPrim().GetMetadata( g_legacyAdapterLabelToken, &metadataValue ) && metadataValue.Get() ) { - newShader->blindData()->writable()[ IECoreScene::ShaderNetworkAlgo::componentConnectionAdapterLabel() ] = new IECore::BoolData( true ); + newShader->blindData()->writable()[ g_legacyAdapterLabelString ] = new IECore::BoolData( true ); } shaderNetwork.addShader( handle, std::move( newShader ) ); @@ -297,7 +302,7 @@ IECoreScene::ShaderNetwork::Parameter readShaderNetworkWalk( const pxr::SdfPath IECoreScene::ConstShaderNetworkPtr adaptShaderNetworkForWriting( const IECoreScene::ShaderNetwork *shaderNetwork ) { IECoreScene::ShaderNetworkPtr result = shaderNetwork->copy(); - IECoreScene::ShaderNetworkAlgo::expandSplines( result.get() ); + IECoreScene::ShaderNetworkAlgo::expandRamps( result.get() ); IECoreScene::ShaderNetworkAlgo::addComponentConnectionAdapters( result.get() ); return result; } @@ -524,7 +529,7 @@ IECoreScene::ShaderNetworkPtr IECoreUSD::ShaderAlgo::readShaderNetwork( const px result->setOutput( outputHandle ); IECoreScene::ShaderNetworkAlgo::removeComponentConnectionAdapters( result.get() ); - IECoreScene::ShaderNetworkAlgo::collapseSplines( result.get() ); + IECoreScene::ShaderNetworkAlgo::collapseRamps( result.get() ); return result; } diff --git a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py index 3c0f6f8c27..19041a3586 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py @@ -2917,12 +2917,13 @@ def testShaders( self ) : surface.parameters["c"] = IECore.StringData( "42" ) surface.parameters["d"] = IECore.Color3fData( imath.Color3f( 3 ) ) surface.parameters["e"] = IECore.V3fVectorData( [ imath.V3f( 7 ) ] ) - surface.parameters["f"] = IECore.SplineffData( IECore.Splineff( IECore.CubicBasisf.bSpline(), - ( ( 0, 1 ), ( 10, 2 ), ( 20, 0 ), ( 21, 2 ) ) ) - ) - surface.parameters["g"] = IECore.SplinefColor3fData( IECore.SplinefColor3f( IECore.CubicBasisf.linear(), - ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ) ) ) - ) + surface.parameters["f"] = IECore.RampffData( IECore.Rampff( + ( ( 0, 1 ), ( 10, 2 ), ( 20, 0 ), ( 21, 2 ) ), IECore.RampInterpolation.BSpline + ) ) + surface.parameters["g"] = IECore.RampfColor3fData( IECore.RampfColor3f( + ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ) ), + IECore.RampInterpolation.Linear + ) ) add1 = IECoreScene.Shader( "add", "ai:shader" ) add1.parameters["b"] = IECore.FloatData( 3.0 ) @@ -2978,11 +2979,13 @@ def testShaders( self ) : dest.parameters["a"] = IECore.Color3fData( imath.Color3f( 0.0 ) ) dest.parameters["b"] = IECore.Color3fData( imath.Color3f( 0.0 ) ) dest.parameters["c"] = IECore.FloatData( 0.0 ) - dest.parameters["sf"] = IECore.SplineffData( IECore.Splineff( IECore.CubicBasisf.catmullRom(), - ( ( 0, 1 ), ( 10, 2 ), ( 20, 0 ), ( 30, 1 ) ) + dest.parameters["sf"] = IECore.RampffData( IECore.Rampff( + ( ( 0, 1 ), ( 10, 2 ), ( 20, 0 ), ( 30, 1 ) ), + IECore.RampInterpolation.CatmullRom ) ) - dest.parameters["sc"] = IECore.SplinefColor3fData( IECore.SplinefColor3f( IECore.CubicBasisf.linear(), - ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ) ) + dest.parameters["sc"] = IECore.RampfColor3fData( IECore.RampfColor3f( + ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ) ), + IECore.RampInterpolation.Linear ) ) componentConnectionNetwork = IECoreScene.ShaderNetwork() @@ -3016,19 +3019,19 @@ def testShaders( self ) : ) ) componentConnectionNetwork.setOutput( IECoreScene.ShaderNetwork.Parameter( "dest", "" ) ) - # Float to spline element connection + # Float to ramp element connection componentConnectionNetwork.addConnection( IECoreScene.ShaderNetwork.Connection( IECoreScene.ShaderNetwork.Parameter( "source1", "out" ), IECoreScene.ShaderNetwork.Parameter( "dest", "sf[3].y" ) ) ) - # Color to spline element connection + # Color to ramp element connection componentConnectionNetwork.addConnection( IECoreScene.ShaderNetwork.Connection( IECoreScene.ShaderNetwork.Parameter( "source3", "out" ), IECoreScene.ShaderNetwork.Parameter( "dest", "sc[2].y" ) ) ) - # Float to spline element component connection + # Float to ramp element component connection componentConnectionNetwork.addConnection( IECoreScene.ShaderNetwork.Connection( IECoreScene.ShaderNetwork.Parameter( "source1", "out" ), IECoreScene.ShaderNetwork.Parameter( "dest", "sc[0].y.g" ) diff --git a/include/IECore/DataAlgo.inl b/include/IECore/DataAlgo.inl index ad64a51e2c..d4ecc141ab 100644 --- a/include/IECore/DataAlgo.inl +++ b/include/IECore/DataAlgo.inl @@ -39,6 +39,7 @@ #include "IECore/PathMatcherData.h" #include "IECore/SimpleTypedData.h" #include "IECore/SplineData.h" +#include "IECore/RampData.h" #include "IECore/TransformationMatrixData.h" #include "IECore/VectorTypedData.h" @@ -132,6 +133,12 @@ typename std::invoke_result_t dispatch( Data *data, F &&fu return functor( static_cast( data ), std::forward( args )... ); case SplinefColor4fDataTypeId : return functor( static_cast( data ), std::forward( args )... ); + case RampffDataTypeId : + return functor( static_cast( data ), std::forward( args )... ); + case RampfColor3fDataTypeId : + return functor( static_cast( data ), std::forward( args )... ); + case RampfColor4fDataTypeId : + return functor( static_cast( data ), std::forward( args )... ); case DateTimeDataTypeId : return functor( static_cast( data ), std::forward( args )... ); case BoolVectorDataTypeId : @@ -286,6 +293,12 @@ typename std::invoke_result_t dispatch( const Data * return functor( static_cast( data ), std::forward( args )... ); case SplinefColor4fDataTypeId : return functor( static_cast( data ), std::forward( args )... ); + case RampffDataTypeId : + return functor( static_cast( data ), std::forward( args )... ); + case RampfColor3fDataTypeId : + return functor( static_cast( data ), std::forward( args )... ); + case RampfColor4fDataTypeId : + return functor( static_cast( data ), std::forward( args )... ); case DateTimeDataTypeId : return functor( static_cast( data ), std::forward( args )... ); case BoolVectorDataTypeId : diff --git a/include/IECore/Ramp.h b/include/IECore/Ramp.h new file mode 100644 index 0000000000..3d0ae98462 --- /dev/null +++ b/include/IECore/Ramp.h @@ -0,0 +1,146 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2025, Image Engine Design Inc. 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 Image Engine Design 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECORE_RAMP_H +#define IECORE_RAMP_H + +#include "IECore/Export.h" +#include "IECore/Spline.h" +#include "IECore/MessageHandler.h" + +IECORE_PUSH_DEFAULT_VISIBILITY +#include "Imath/ImathColor.h" +IECORE_POP_DEFAULT_VISIBILITY + +#include + +namespace IECore +{ + +// This lives outside the class because we don't want multiple incompatible templated versions of +// the same enum floating around +enum class RampInterpolation +{ + Linear = 0, + CatmullRom = 1, + BSpline = 2, + MonotoneCubic = 3, + Constant = 4, +}; + +/// A Ramp represents a spline-like curve as it is represented in a simple UI: with a set of independent +/// control points, and an interpolation type selected from RampInterpolation. +/// +/// Rather than storing the lower level IECore::Spline*, we now store this Ramp type in shader networks, +/// and only convert to the lower level class with the evaluator() function when evaluation is needed. +/// +/// This was chosen as superior to IECore::Spline* because IECoreS::spline* requires duplicating the +/// end points in order to make the curve reach the first and last control point. +template +class IECORE_EXPORT Ramp +{ + + public : + + using XType = X; + using YType = Y; + + using PointContainer = std::multimap; + using Point = typename PointContainer::value_type; + + Ramp() : interpolation( RampInterpolation::CatmullRom ) + { + } + + Ramp( const PointContainer &p, RampInterpolation i ) + : points( p ), interpolation( i ) + { + } + + PointContainer points; + RampInterpolation interpolation; + + + // Convert to Cortex Spline + // In the future, IECore::Spline may be replaced with IECore::SplineEvaluator, and this + // function would be the only way to setup one. + IECore::Spline evaluator() const; + + // Convert to and from a set of arguments that could be passed to a pair of spline() and + // splineinverse() functions in OSL. This can be useful in converting ramps to parameters + // for OSL shaders. + // + // Some shader libraries use these arguments directly as shader parameters ( i.e. Gaffer ). + // Some shader libraries preprocess shader parameters before passing them to spline(), + // so they don't need some aspects of this conversion ( like endpoint duplication ), but + // the extra endpoint duplication doesn't cause problems ( i.e. PRMan ). + // Some shader libraries are doing their own thing, implementing their own custom math, + // but convention is still similar enough that these function can be a useful building + // block in converting to something that mostly works ( i.e. 3delight ). + void fromOSL( const std::string &basis, const std::vector &positions, const std::vector &values, const std::string &identifier ); + void toOSL( std::string &basis, std::vector &positions, std::vector &values ) const; + + /// The number of times `toOSL()` repeats the initial point. + int oslStartPointMultiplicity() const; + + // In Cortex 10.6 and earlier, shader parameters were represented uing IECore::Spline*Data instead of + // IECore::Ramp*Data. This is used in converting SCC files to the new standard. + // \todo : This can probably be removed in the next major version - we're not actually aware of any + // significant users of Cortex who both use SCC files, and cache shaders, so this compatibility shim + // is only needed theoretically. + void fromDeprecatedSpline( const IECore::Spline &deprecated ); + + bool operator==( const Ramp &rhs ) const; + bool operator!=( const Ramp &rhs ) const; + +}; + +using Rampff = Ramp; +using RampfColor3f = Ramp; +using RampfColor4f = Ramp; + +template +inline void murmurHashAppend( IECore::MurmurHash &h, const Ramp &data ) +{ + h.append( data.interpolation ); + for ( auto &p : data.points ) + { + h.append( p.first ); + h.append( p.second ); + } +} + +} // namespace IECore + +#endif // IECORE_RAMP_H diff --git a/include/IECore/RampData.h b/include/IECore/RampData.h new file mode 100644 index 0000000000..b684b68927 --- /dev/null +++ b/include/IECore/RampData.h @@ -0,0 +1,52 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2025, Image Engine Design Inc. 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 Image Engine Design 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECORE_RAMPDATA_H +#define IECORE_RAMPDATA_H + +#include "IECore/Ramp.h" +#include "IECore/TypedData.h" + +namespace IECore +{ + +// Ramp data types. + +IECORE_DECLARE_TYPEDDATA( RampffData, Rampff, void, SharedDataHolder ) +IECORE_DECLARE_TYPEDDATA( RampfColor3fData, RampfColor3f, void, SharedDataHolder ) +IECORE_DECLARE_TYPEDDATA( RampfColor4fData, RampfColor4f, void, SharedDataHolder ) + +} + +#endif // IECORE_RAMPDATA_H diff --git a/include/IECore/SplineParameter.h b/include/IECore/SplineParameter.h index 8af24689f5..f490e8a8b2 100644 --- a/include/IECore/SplineParameter.h +++ b/include/IECore/SplineParameter.h @@ -41,6 +41,12 @@ namespace IECore { +// NOTE : Using Spline*Data has basically been deprecated in favour of Ramp*Data. +// Setting up shader data this way will no longer work with renderer backends. +// +// We do not plan to update this because Parameter itself is on the way to +// deprecation ( in favour of using Gaffer to build UIs ). + typedef TypedParameter SplineffParameter; typedef TypedParameter SplineddParameter; typedef TypedParameter SplinefColor3fParameter; diff --git a/include/IECore/TypeIds.h b/include/IECore/TypeIds.h index f95c8a2ad4..82fb4252b1 100644 --- a/include/IECore/TypeIds.h +++ b/include/IECore/TypeIds.h @@ -203,9 +203,9 @@ enum TypeId YUVImageWriterTypeId = 265, DateTimeDataTypeId = 269, DateTimeParameterTypeId = 270, - TimeDurationDataTypeId = 272, // Obsolete - TimeDurationParameterTypeId = 273, // Obsolete - TimePeriodDataTypeId = 274, // Obsolete + RampffDataTypeId = 272, + RampfColor3fDataTypeId = 273, + RampfColor4fDataTypeId = 274, TimePeriodParameterTypeId = 275, // Obsolete FrameListTypeId = 279, EmptyFrameListTypeId = 280, diff --git a/include/IECore/TypeTraits.h b/include/IECore/TypeTraits.h index c0590715f6..14f1ebf03d 100644 --- a/include/IECore/TypeTraits.h +++ b/include/IECore/TypeTraits.h @@ -40,6 +40,8 @@ #include "IECore/SimpleTypedData.h" #include "IECore/Spline.h" #include "IECore/SplineData.h" +#include "IECore/Ramp.h" +#include "IECore/RampData.h" #include "IECore/TransformationMatrixData.h" #include "IECore/VectorTypedData.h" @@ -293,6 +295,14 @@ template struct IsSpline< const Spline > : public /// IsSplineTypedData template< typename T > struct IsSplineTypedData : boost::mpl::and_< IsTypedData, IsSpline< typename ValueType::type > > {}; +/// IsRamp +template struct IsRamp : public boost::false_type {}; +template struct IsRamp< Ramp > : public boost::true_type {}; +template struct IsRamp< const Ramp > : public boost::true_type {}; + +/// IsRampTypedData +template< typename T > struct IsRampTypedData : boost::mpl::and_< IsTypedData, IsRamp< typename ValueType::type > > {}; + /// IsStringVectorTypeData template struct IsStringVectorTypedData : public boost::false_type {}; template<> struct IsStringVectorTypedData< TypedData > > : public boost::true_type {}; diff --git a/include/IECoreImage/SplineToImage.h b/include/IECoreImage/SplineToImage.h index a17d6674f9..b8619fbc06 100644 --- a/include/IECoreImage/SplineToImage.h +++ b/include/IECoreImage/SplineToImage.h @@ -54,6 +54,8 @@ namespace IECoreImage /// This Op creates ImagePrimitives from SplineData. /// \todo Different projections would be nice. +/// \todo If we wanted to keep this up to date, it should probably take RampData instead +/// of SplineData, but ImagePrimitive is on the path to deprecation anyway. /// \ingroup imageProcessingGroup class IECOREIMAGE_API SplineToImage : public IECore::Op { diff --git a/include/IECorePython/RampBinding.h b/include/IECorePython/RampBinding.h new file mode 100644 index 0000000000..e1beb90fba --- /dev/null +++ b/include/IECorePython/RampBinding.h @@ -0,0 +1,47 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2025, Image Engine Design Inc. 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 Image Engine Design 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECOREPYTHON_RAMPBINDING_H +#define IECOREPYTHON_RAMPBINDING_H + +#include "IECorePython/Export.h" + +namespace IECorePython +{ + +IECOREPYTHON_API void bindRamp(); + +} + +#endif // IECOREPYTHON_RAMPBINDING_H diff --git a/include/IECorePython/RampDataBinding.h b/include/IECorePython/RampDataBinding.h new file mode 100644 index 0000000000..75173a8f02 --- /dev/null +++ b/include/IECorePython/RampDataBinding.h @@ -0,0 +1,45 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2008-2010, Image Engine Design Inc. 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 Image Engine Design 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECOREPYTHON_RAMPDATABINDING_H +#define IECOREPYTHON_RAMPDATABINDING_H + +#include "IECorePython/Export.h" + +namespace IECorePython +{ +IECOREPYTHON_API void bindRampData(); +} + +#endif // IECOREPYTHON_RAMPDATABINDING_H diff --git a/include/IECoreScene/ShaderNetworkAlgo.h b/include/IECoreScene/ShaderNetworkAlgo.h index 0e8998ad5a..4556f53367 100644 --- a/include/IECoreScene/ShaderNetworkAlgo.h +++ b/include/IECoreScene/ShaderNetworkAlgo.h @@ -95,9 +95,6 @@ IECORESCENE_API void registerJoinAdapter( const std::string &destinationShaderTy /// Removes an adapter registration. IECORESCENE_API void deregisterJoinAdapter( const std::string &destinationShaderType, IECore::TypeId destinationParameterType ); -/// \deprecated -IECORESCENE_API const IECore::InternedString &componentConnectionAdapterLabel(); - /// Converts various aspects of how shaders are stored to be ready to pass directly to OSL. /// The `oslVersion` argument is used to determine how conversion is performed, and should be passed a /// value of `OSL_VERSION`. Conversions include: @@ -106,56 +103,39 @@ IECORESCENE_API const IECore::InternedString &componentConnectionAdapterLabel(); /// For OSL prior to 1.10, intermediate shaders are inserted to emulate connections between components. /// For later versions, no new shaders are inserted, but components are renamed from our `.x, .y, .z` /// suffixes to OSL's `[0], [1], [2]` suffixes. -/// - Splines -/// We support SplineData as a parameter type. For OSL, these must be converted to 3 parameters named -/// `Positions`, `Values` and `Basis`. We also support input -/// connections to spline Y values, specified as `[N].y`, which currently must be implemented +/// - Ramps +/// We support RampData as a parameter type. For OSL, these must be converted to 3 parameters named +/// `Positions`, `Values` and `Basis`. We also support input +/// connections to ramp Y values, specified as `[N].y`, which currently must be implemented /// using an adapter shader. IECORESCENE_API void convertToOSLConventions( ShaderNetwork *network, int oslVersion ); -/// Finds connections involving the individual components of point/color parameters, and converts them -/// for use with OSL. The `oslVersion` argument is used to determine how conversion is performed, -/// and should be passed a value of `OSL_VERSION`. For OSL prior to 1.10, intermediate shaders are -/// inserted to emulate connections between components. For later versions, no new shaders are inserted, but -/// components are renamed from our `.x, .y, .z` suffixes to OSL's `[0], [1], [2]` suffixes. -/// \deprecated: Use convertToOSLConventions instead -IECORESCENE_API void convertOSLComponentConnections( ShaderNetwork *network ); -IECORESCENE_API void convertOSLComponentConnections( ShaderNetwork *network, int oslVersion ); - /// Converts from the legacy ObjectVector format previously used to represent shader networks. IECORESCENE_API ShaderNetworkPtr convertObjectVector( const IECore::ObjectVector *network ); -/// We use a convention where ramps are represented by a single SplineData in Cortex, but must be expanded +/// We use a convention where ramps are represented by a single RampData in Cortex, but must be expanded /// out into basic types when being passed to a renderer. We need two functions to convert back and forth. - -/// Look throughout the network for parameters matching our spline convention, for any possible : +/// Look throughout the network for parameters matching our ramp convention, for any possible : /// Positions, a float vector parameter /// Values, a vector of a value type, such as float or color /// Basis, a string parameter /// For each set of parameters found matching this convention, the 3 parameters will be replaced with one -/// spline parameter named . If input connections are represented using an adapter shader, they -/// will be converted to direct connections to the spline using our support for spline element +/// ramp parameter named . If input connections are represented using an adapter shader, they +/// will be converted to direct connections to the ramp using our support for ramp element /// connections. /// If `targetPrefix` is given, only translates connections to shaders with a type starting with this string -IECORESCENE_API void collapseSplines( ShaderNetwork *network, std::string targetPrefix = "" ); +IECORESCENE_API void collapseRamps( ShaderNetwork *network, std::string targetPrefix = "" ); -/// Look throughout the network for spline parameters. If any are found, they will be expanded out into +/// Look throughout the network for ramp parameters. If any are found, they will be expanded out into /// 3 parameters named Positions, Values and Basis. -/// We also support input connections to spline Y values, specified as `[N].y`, which currently +/// We also support input connections to ramp Y values, specified as `[N].y`, which currently /// must be implemented by inserting an adapter shader. /// If `targetPrefix` is given, only translates connections to shaders with a type starting with this string -IECORESCENE_API void expandSplines( ShaderNetwork *network, std::string targetPrefix = "" ); - - -/// \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, and supports -/// different spline conventions for different renderers' shader libraries -IECORESCENE_API IECore::ConstCompoundDataPtr expandSplineParameters( const IECore::ConstCompoundDataPtr& parametersData ); +IECORESCENE_API void expandRamps( ShaderNetwork *network, std::string targetPrefix = "" ); +/// Used when dealing with SCC files written before Cortex 10.7, which used Spline*Data instead of Ramp*Data +IECORESCENE_API void convertDeprecatedSplines( ShaderNetwork *network ); } // namespace ShaderNetworkAlgo diff --git a/python/IECore/DataTraits.py b/python/IECore/DataTraits.py index 6b079031cd..98f3d6bbeb 100644 --- a/python/IECore/DataTraits.py +++ b/python/IECore/DataTraits.py @@ -203,6 +203,10 @@ def isSequenceDataType(obj): IECore.SplinefColor3fData: ( IECore.SplinefColor3f, True ), IECore.SplinefColor4fData: ( IECore.SplinefColor4f, True ), + IECore.RampffData: ( IECore.Rampff, True ), + IECore.RampfColor3fData: ( IECore.RampfColor3f, True ), + IECore.RampfColor4fData: ( IECore.RampfColor4f, True ), + IECore.DateTimeData: ( datetime.datetime, True ), IECore.TimeCodeData: ( IECore.TimeCode, True ), IECore.PathMatcherData: ( IECore.PathMatcher, True ), diff --git a/src/IECore/Ramp.cpp b/src/IECore/Ramp.cpp new file mode 100644 index 0000000000..883102464f --- /dev/null +++ b/src/IECore/Ramp.cpp @@ -0,0 +1,547 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2025, Image Engine Design Inc. 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 Image Engine Design 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. +// +////////////////////////////////////////////////////////////////////////// + + +#include "IECore/Ramp.h" +#include "IECore/CubicBasis.h" + +using namespace IECore; + +namespace +{ + +template +inline Y monotoneSlopeCompute( const Y& deltaY1, const Y& deltaY2, const X& deltaX1, const X& deltaX2 ) +{ + // Using this weighted harmonic mean to compute slopes ensures a monotone curve. + // This is apparently a result by Fritsch and Carlson, from here: + // + // F. N. Fritsch and R. E. Carlson + // SIAM Journal on Numerical Analysis + // Vol. 17, No. 2 (Apr., 1980), pp. 238-246 + // + // Haven't actually gotten ahold of this paper, but stackexchange says that it says this, + // and it seems to work quite well. + if( deltaY1 * deltaY2 > 0.0f ) + { + return 3.0f * ( deltaX1 + deltaX2 ) / ( + ( 2.0f * deltaX2 + deltaX1 ) / deltaY1 + + ( deltaX2 + 2.0f * deltaX1 ) / deltaY2 ); + } + else + { + return 0; + } +} + +template<> +inline Imath::Color3f monotoneSlopeCompute<>( + const Imath::Color3f& deltaY1, const Imath::Color3f& deltaY2, + const float& deltaX1, const float& deltaX2 ) +{ + return Imath::Color3f( + monotoneSlopeCompute( deltaY1[0], deltaY2[0], deltaX1, deltaX2 ), + monotoneSlopeCompute( deltaY1[1], deltaY2[1], deltaX1, deltaX2 ), + monotoneSlopeCompute( deltaY1[2], deltaY2[2], deltaX1, deltaX2 ) + ); +} + +template<> +inline Imath::Color4f monotoneSlopeCompute<>( + const Imath::Color4f& deltaY1, const Imath::Color4f& deltaY2, + const float& deltaX1, const float& deltaX2 ) +{ + return Imath::Color4f( + monotoneSlopeCompute( deltaY1[0], deltaY2[0], deltaX1, deltaX2 ), + monotoneSlopeCompute( deltaY1[1], deltaY2[1], deltaX1, deltaX2 ), + monotoneSlopeCompute( deltaY1[2], deltaY2[2], deltaX1, deltaX2 ), + monotoneSlopeCompute( deltaY1[3], deltaY2[3], deltaX1, deltaX2 ) + ); +} + +// This function translates a set of control points for a MonotoneCubic curve into a set of +// bezier control points. The X values are set up to make each segment linear in X, which +// makes the control point behaviour a bit more predictable when used as a color ramp. +// The Y tangents are adjusted to compensate for the discontinuity in the slope of the +// parameterization across control points, which mean that the X/Y tangent is continuous across +// control points. This curve type seems to work quite well in practice for color ramps. +// +// Note that the way we are evaluating this type of curve by using a spline solver on the X +// axis is ridiculously inefficient - the bezier control points are arranged so it's actually +// always linear, and could be quickly solved analyticly. But because we need to store these +// curves in IECore::Spline when evaluating, we don't have any way to specify different +// interpolations for X and Y. So we just use bezier curve with appropriately set handles to +// store our linear X curve. +template +void monotoneCubicCVsToBezierCurve( const typename T::PointContainer &cvs, typename T::PointContainer &result ) +{ + if( cvs.size() < 2 ) + { + result = cvs; + return; + } + + result.clear(); + + // NOTE : It would seem more reasonable to use the slope of the first and last segment for the + // endpoints, instead of clamping to 0. The current argument for clamping to zero is consistency + // with the Htoa ramp + typename T::YType prevSlope(0); + + typename T::PointContainer::const_iterator i = cvs.begin(); + const typename T::Point *p1 = &*i; + i++; + const typename T::Point *p2 = &*i; + i++; + + for(;;) + { + typename T::YType nextSlope; + + + const typename T::Point *pNext = nullptr; + if( i == cvs.end() ) + { + nextSlope = typename T::YType( 0 ); + } + else + { + pNext = &*i; + + typename T::XType xDelta1 = p2->first - p1->first; + typename T::XType xDelta2 = pNext->first - p2->first; + typename T::YType yDelta1 = p2->second - p1->second; + typename T::YType yDelta2 = pNext->second - p2->second; + + nextSlope = monotoneSlopeCompute( yDelta1 / xDelta1, yDelta2 / xDelta2, xDelta1, xDelta2); + + // NOTE : If we copied everything else about this function, but instead just used: + // 0.5 * ( yDelta1 / xDelta1 + yDelta2 / xDelta2 ) + // for the slope here, this would produce a CatmullRom sort of spline, but with the simpler linear + // behaviour of the knot values. This is what Htoa uses for a CatmullRom ramp, and might be a pretty + // useful curve type ( quite possibly more useful than our current CatmullRom, though it would no + // longer correspond to a CatmullRom basis in OSL ). + } + + typename T::XType xDelta = p2->first - p1->first; + + result.insert( *p1 ); + result.insert( typename T::Point( + p1->first + ( 1.0f/3.0f ) * xDelta, + p1->second + (1.0f/3.0f) * prevSlope * xDelta ) ); + result.insert( typename T::Point( + p1->first + ( 2.0f/3.0f ) * xDelta, + p2->second - (1.0f/3.0f) * nextSlope * xDelta ) ); + + if( i == cvs.end() ) + { + break; + } + else + { + p1 = p2; + p2 = pNext; + prevSlope = nextSlope; + i++; + } + } + + result.insert( *p2 ); +} + +template +const typename T::PointContainer *convertMonotoneCubic( const T &source, typename T::PointContainer &storage) +{ + if( source.interpolation == RampInterpolation::MonotoneCubic ) + { + if( source.points.size() > 1 ) + { + } + else + { + storage = source.points; + } + return &storage; + } + else + { + return &source.points; + } +} + +int endPointMultiplicity( IECore::RampInterpolation interpolation ) +{ + int multiplicity = 1; + if( interpolation == IECore::RampInterpolation::CatmullRom ) + { + multiplicity = 2; + } + else if( interpolation == IECore::RampInterpolation::BSpline ) + { + multiplicity = 3; + } + return multiplicity; +} + +std::pair< size_t, size_t > getOSLEndPointDuplication( IECore::RampInterpolation interpolation ) +{ + if( interpolation == IECore::RampInterpolation::CatmullRom ) + { + return std::make_pair( 1, 1 ); + } + else if( interpolation == IECore::RampInterpolation::BSpline ) + { + return std::make_pair( 2, 2 ); + } + else if( interpolation == IECore::RampInterpolation::Linear ) + { + // OSL discards the first and last segment of linear curves + // "To maintain consistency with the other spline types" + // so we need to duplicate the end points to preserve all provided segments + return std::make_pair( 1, 1 ); + } + else if( interpolation == IECore::RampInterpolation::Constant ) + { + // Also, "To maintain consistency", "constant splines ignore the first and the two last + // data values." + return std::make_pair( 1, 2 ); + } + + + return std::make_pair( 0, 0 ); +} + +// Removes start and end points with duplicated X values. +// +// Sources of spline data may or may not contain duplicated end points for a variety of reasons +// ( such as IECore::Splineff having duplicated end points so that spline evaluation will reach +// the final value, or OSL have duplicated end points even for constant and linear splines ). +// +// The spline UI's that Gaffer uses to interact with splines don't support duplicated end +// points well, so regardless of why they are there, this function will remove them. +template +void trimEndPoints( PointContainer &points ) +{ + if( points.empty() ) + { + return; + } + + // Count how many initial points have an X value matching the first point + typename PointContainer::const_iterator startDuplicates = ++points.begin(); + while( startDuplicates != points.end() && startDuplicates->first == points.begin()->first ) + { + startDuplicates++; + } + // We need to keep one of these points, the rest are duplicates and should be removed + startDuplicates--; + + points.erase( points.begin(), startDuplicates ); + + // Count how many points have an X value matching the last point + typename PointContainer::const_reverse_iterator endDuplicates = ++points.rbegin(); + while( endDuplicates != points.rend() && endDuplicates->first == points.rbegin()->first ) + { + endDuplicates++; + } + + // We need to keep one of these points, the rest are duplicates and should be removed + endDuplicates--; + + points.erase( endDuplicates.base(), points.rbegin().base() ); + + // This originally indicated whether the end point duplication matched the expected multiplicity + // for a certain interpolation. We no longer make assumptions about how the input data handles end + // point multiplicity, and instead just remove any duplicates, so we no longer consider any inputs + // to be invalid. + return; +} + +} + +namespace IECore +{ + +template +IECore::Spline Ramp::evaluator() const +{ + using ResultType = IECore::Spline; + ResultType result; + + result.points = points; + + if( interpolation == RampInterpolation::Linear ) + { + result.basis = ResultType::Basis::linear(); + } + else if( interpolation == RampInterpolation::CatmullRom ) + { + result.basis = ResultType::Basis::catmullRom(); + } + else if( interpolation == RampInterpolation::BSpline ) + { + result.basis = ResultType::Basis::bSpline(); + } + else if( interpolation == RampInterpolation::MonotoneCubic ) + { + monotoneCubicCVsToBezierCurve< Ramp >( points, result.points ); + result.basis = ResultType::Basis::bezier(); + } + else if( interpolation == RampInterpolation::Constant ) + { + result.basis = ResultType::Basis::constant(); + } + + int multiplicity = endPointMultiplicity( interpolation ); + + if( multiplicity && result.points.size() ) + { + for( int i = 0; i < multiplicity - 1; ++i ) + { + result.points.insert( *result.points.begin() ); + result.points.insert( *result.points.rbegin() ); + } + } + + return result; +} + +template +void Ramp::fromOSL( const std::string &basis, const std::vector &positions, const std::vector &values, const std::string &identifier ) +{ + points.clear(); + size_t n = std::min( positions.size(), values.size() ); + + if( basis == "bezier" ) + { + for( size_t i = 0; i < n; i += 3 ) + { + points.insert( Point( positions[i], values[i] ) ); + } + + interpolation = RampInterpolation::MonotoneCubic; + + std::vector testPositions; + std::vector testValues; + std::string testBasis; + toOSL( testBasis, testPositions, testValues ); + if( !( testPositions == positions && testValues == values ) ) + { + IECore::msg( IECore::MessageHandler::Warning, "Ramp", "While loading shader parameter " + identifier + " found bezier curve that cannot be represented in Gaffer. Using most similar MonotoneCubic curve instead." ); + } + return; + } + else + { + for( size_t i = 0; i < n; ++i ) + { + points.insert( Point( positions[i], values[i] ) ); + } + } + + if( basis == "bspline" ) + { + interpolation = RampInterpolation::BSpline; + } + else if( basis == "linear" ) + { + interpolation = RampInterpolation::Linear; + } + else if( basis == "constant" ) + { + interpolation = RampInterpolation::Constant; + } + else if( basis == "monotonecubic" ) + { + // 3delight actually supports monotonecubic, so it's possible we could see an input + // directly using this interpolation. + interpolation = RampInterpolation::MonotoneCubic; + } + else + { + interpolation = RampInterpolation::CatmullRom; + } + + trimEndPoints( points ); + +} + +template +void Ramp::toOSL( std::string &basis, std::vector &positions, std::vector &values ) const +{ + PointContainer storage; + const PointContainer *currentPoints = &points; + + if( interpolation == RampInterpolation::MonotoneCubic ) + { + monotoneCubicCVsToBezierCurve< Ramp >( points, storage ); + currentPoints = &storage; + basis = "bezier"; + } + else if( interpolation == RampInterpolation::BSpline ) + { + basis = "bspline"; + } + else if( interpolation == RampInterpolation::Linear ) + { + basis = "linear"; + } + else if( interpolation == RampInterpolation::Constant ) + { + // Also, "To maintain consistency", "constant splines ignore the first and the two last + // data values." + basis = "constant"; + } + else + { + basis = "catmull-rom"; + } + auto [ duplicateStartPoints, duplicateEndPoints ] = getOSLEndPointDuplication( interpolation ); + + positions.reserve( currentPoints->size() ); + values.reserve( currentPoints->size() + duplicateStartPoints + duplicateEndPoints ); + + if( currentPoints->size() ) + { + for( size_t i = 0; i < duplicateStartPoints; i++ ) + { + positions.push_back( currentPoints->begin()->first ); + values.push_back( currentPoints->begin()->second ); + } + } + for( auto &it : *currentPoints ) + { + positions.push_back( it.first ); + values.push_back( it.second ); + } + if( currentPoints->size() ) + { + for( size_t i = 0; i < duplicateEndPoints; i++ ) + { + positions.push_back( currentPoints->rbegin()->first ); + values.push_back( currentPoints->rbegin()->second ); + } + } +} + +template +int Ramp::oslStartPointMultiplicity() const +{ + if( interpolation == RampInterpolation::MonotoneCubic ) + { + // I guess the only way to handle this properly would be to do the end point duplication + // and conversion from monotone to bezier in our OSL shaders ( like the PRMan and 3delight + // shader libraries do ). Except that the PRMan approach has a potentially significant + // downside that if the inputs are varying, the array conversion code can't be constant + // folded, and you would end up doing the array processing per-shading point. 3delight + // appears to have implemented their own spline handling, which may not have this problem, + // but we don't want to do this ourselves. For now, not handling inputs to monotoneCubic + // ramps seems pretty reasonable. + throw IECore::Exception( "Cannot connect adaptors to ramp when using monotoneCubic interpolation" ); + } + return getOSLEndPointDuplication( interpolation ).first; +} + +template +void Ramp::fromDeprecatedSpline( const IECore::Spline &deprecated ) +{ + // This is sort of similar to fromOSL, except the source is an old Cortex Spline instead of + // separate position and value vectors. In theory, there might be some way to share a bit more + // code here, but it's probably not worth refactoring for the sake of this method, which it + // should be possible to remove in the future ( once we no longer need to support old SCC files ) + + if( deprecated.basis == CubicBasis::bezier() ) + { + int count = 0; + for( const auto &i : deprecated.points ) + { + if( ( count % 3 ) == 0 ) + { + points.insert( i ); + } + count++; + } + + interpolation = RampInterpolation::MonotoneCubic; + + // Unlike fromOSL, we don't check whether monotoneCubic actually matches the original bezier + // ... if the source data is from an scc produced by old Gaffer, then it should have come from + // a monotoneCubic. + + return; + } + else + { + points = deprecated.points; + } + + if( deprecated.basis == CubicBasis::bSpline() ) + { + interpolation = RampInterpolation::BSpline; + } + else if( deprecated.basis == CubicBasis::linear() ) + { + interpolation = RampInterpolation::Linear; + } + else if( deprecated.basis == CubicBasis::constant() ) + { + interpolation = RampInterpolation::Constant; + } + else + { + interpolation = RampInterpolation::CatmullRom; + } + + trimEndPoints( points ); + +} + +template +inline bool Ramp::operator==( const Ramp &rhs ) const +{ + return interpolation == rhs.interpolation && points == rhs.points; +} + +template +inline bool Ramp::operator!=( const Ramp &rhs ) const +{ + return !operator==( rhs ); +} + +// explicit instantiation +template class Ramp; +template class Ramp; +template class Ramp; + +} // namespace IECore diff --git a/src/IECore/RampData.cpp b/src/IECore/RampData.cpp new file mode 100644 index 0000000000..95815edbe4 --- /dev/null +++ b/src/IECore/RampData.cpp @@ -0,0 +1,127 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2025, Image Engine Design Inc. 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 Image Engine Design 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. +// +////////////////////////////////////////////////////////////////////////// + +#include "IECore/RampData.h" + +#include "IECore/TypedData.inl" + +#include + +using namespace std; +using namespace IECore; + +namespace IECore +{ + +static IndexedIO::EntryID g_interpolationEntry("interpolation"); +static IndexedIO::EntryID g_xEntry("x"); +static IndexedIO::EntryID g_yEntry("y"); + +IECORE_RUNTIMETYPED_DEFINETEMPLATESPECIALISATION( RampffData, RampffDataTypeId ) +IECORE_RUNTIMETYPED_DEFINETEMPLATESPECIALISATION( RampfColor3fData, RampfColor3fDataTypeId ) +IECORE_RUNTIMETYPED_DEFINETEMPLATESPECIALISATION( RampfColor4fData, RampfColor4fDataTypeId ) + +#define SPECIALISE( TNAME, YBASETYPE, YBASESIZE ) \ + \ + template<> \ + void TNAME::save( SaveContext *context ) const \ + { \ + Data::save( context ); \ + IndexedIOPtr container = context->container( staticTypeName(), 0 ); \ + const ValueType &s = readable(); \ + \ + container->write( g_interpolationEntry, (int)s.interpolation); \ + \ + vector x; \ + vector y; \ + ValueType::PointContainer::const_iterator it; \ + for( it=s.points.begin(); it!=s.points.end(); it++ ) \ + { \ + x.push_back( it->first ); \ + y.push_back( it->second ); \ + } \ + container->write( g_xEntry, &(x[0]), x.size() ); \ + container->write( g_yEntry, (const YBASETYPE*)&(y[0]), y.size() * YBASESIZE ); \ + } \ + \ + template<> \ + void TNAME::load( LoadContextPtr context ) \ + { \ + Data::load( context ); \ + unsigned int v = 0; \ + ConstIndexedIOPtr container = context->container( staticTypeName(), v ); \ + ValueType &s = writable(); \ + \ + int interpolationInt; \ + container->read( g_interpolationEntry, interpolationInt ); \ + s.interpolation = (RampInterpolation)interpolationInt; \ + \ + vector x; \ + vector y; \ + IndexedIO::Entry e = container->entry( "x" ); \ + x.resize( e.arrayLength() ); \ + y.resize( e.arrayLength() ); \ + ValueType::XType *xp = &(x[0]); \ + container->read( g_xEntry, xp, e.arrayLength() ); \ + YBASETYPE *yp = (YBASETYPE *)&(y[0]); \ + container->read( g_yEntry, yp, e.arrayLength() * YBASESIZE ); \ + \ + s.points.clear(); \ + for( unsigned i=0; i \ + void TNAME::memoryUsage( Object::MemoryAccumulator &accumulator ) const \ + { \ + Data::memoryUsage( accumulator ); \ + const ValueType &s = readable(); \ + ValueType::PointContainer::const_iterator it; \ + size_t m = s.points.size() * ( sizeof( ValueType::XType ) + sizeof( ValueType::YType ) ); \ + m += sizeof( ValueType ); \ + accumulator.accumulate( m ); \ + } \ + +SPECIALISE( RampffData, float, 1 ) +SPECIALISE( RampfColor3fData, float, 3 ) +SPECIALISE( RampfColor4fData, float, 4 ) + +// Instantiations +template class TypedData; +template class TypedData; +template class TypedData; + +} diff --git a/src/IECoreGL/ShaderStateComponent.cpp b/src/IECoreGL/ShaderStateComponent.cpp index 0fc5f7912b..0392123cfc 100644 --- a/src/IECoreGL/ShaderStateComponent.cpp +++ b/src/IECoreGL/ShaderStateComponent.cpp @@ -132,6 +132,9 @@ class ShaderStateComponent::Implementation : public IECore::RefCounted if( p->type == GL_SAMPLER_2D ) { ConstTexturePtr texture = nullptr; + // NOTE : Shader parameters stored as SplineData are deprecated ( we now use Ramp ), + // however we're not updating this code, because we're not aware of anywhere it's currently + // used if( it->second->typeId() == (IECore::TypeId)IECoreImage::ImagePrimitiveTypeId || it->second->typeId() == IECore::CompoundDataTypeId || diff --git a/src/IECorePython/RampBinding.cpp b/src/IECorePython/RampBinding.cpp new file mode 100644 index 0000000000..b54cd280db --- /dev/null +++ b/src/IECorePython/RampBinding.cpp @@ -0,0 +1,145 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2025, Image Engine Design Inc. 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 Image Engine Design 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. +// +////////////////////////////////////////////////////////////////////////// + +// This include needs to be the very first to prevent problems with warnings +// regarding redefinition of _POSIX_C_SOURCE +#include "boost/python.hpp" + +#include "IECorePython/RampBinding.h" + +#include "IECorePython/IECoreBinding.h" + +#include "IECore/Ramp.h" + +using namespace boost::python; +using namespace Imath; +using namespace std; +using namespace IECore; + +namespace IECorePython +{ + +template +std::string rampRepr( object x ) +{ + std::stringstream s; + const std::string name = extract( x.attr( "__class__").attr( "__name__" ) ); + s << "IECore." << name << "( "; + const T ramp = extract( x ); + s << "("; + int i = 0; + int l = ramp.points.size(); + typename T::PointContainer::const_iterator it; + for( it = ramp.points.begin(); it != ramp.points.end(); it++, i++ ) + { + // TODO - without this const_cast I get a link error because the const version of repr + // hasn't been defined + s << " ( " << it->first << ", " << IECorePython::repr( const_cast( it->second ) ) << " )"; + if( i!=l-1 ) + { + s << ","; + } + } + s << "), "; + const std::string interpStr = extract( object( ramp.interpolation ).attr( "__str__")() ); + s << "IECore.RampInterpolation." << interpStr; + s << ")"; + return s.str(); +} + +template +T *rampConstruct( object o, const RampInterpolation &interpolation ) +{ + typename T::PointContainer points; + int s = extract( o.attr( "__len__" )() ); + for( int i=0; i( e.attr( "__len__" )() ); + if( es!=2 ) + { + throw IECore::Exception( "Each entry in the point sequence must contain two values." ); + } + object xo = e[0]; + object yo = e[1]; + float x = extract( xo ); + typename T::YType y = extract( yo ); + points.insert( typename T::PointContainer::value_type( x, y ) ); + } + return new T( points, interpolation ); +} + +template +boost::python::tuple rampPoints( const T &s ) +{ + boost::python::list p; + typename T::PointContainer::const_iterator it; + for( it=s.points.begin(); it!=s.points.end(); it++ ) + { + p.append( make_tuple( it->first, it->second ) ); + } + return boost::python::tuple( p ); +} + +template +void bindRampTemplate( const char *name) +{ + class_( name ) + .def( "__init__", make_constructor( &rampConstruct ) ) + .def( "__repr__", &rampRepr ) + .def( "points", &rampPoints, "Read only access to the control points as a tuple of tuples of ( x, y ) pairs." ) + .def_readwrite("interpolation", &T::interpolation) + .def( self==self ) + .def( self!=self ) + .def( "evaluator", &T::evaluator ) + ; +} + + +void bindRamp() +{ + enum_( "RampInterpolation" ) + .value( "Linear", RampInterpolation::Linear ) + .value( "CatmullRom", RampInterpolation::CatmullRom ) + .value( "BSpline", RampInterpolation::BSpline ) + .value( "MonotoneCubic", RampInterpolation::MonotoneCubic ) + .value( "Constant", RampInterpolation::Constant ) + ; + + bindRampTemplate( "Rampff" ); + bindRampTemplate( "RampfColor3f" ); + bindRampTemplate( "RampfColor4f" ); +} + +} diff --git a/src/IECorePython/RampDataBinding.cpp b/src/IECorePython/RampDataBinding.cpp new file mode 100644 index 0000000000..28c5a1b6a0 --- /dev/null +++ b/src/IECorePython/RampDataBinding.cpp @@ -0,0 +1,110 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2008-2013, Image Engine Design Inc. 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 Image Engine Design 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. +// +////////////////////////////////////////////////////////////////////////// + +#include "boost/python.hpp" + +#include "IECorePython/RampDataBinding.h" + +#include "IECorePython/IECoreBinding.h" +#include "IECorePython/RunTimeTypedBinding.h" +#include "IECorePython/SimpleTypedDataBinding.h" + +#include "IECore/RampData.h" + +#include "boost/python/make_constructor.hpp" + +#include + +using namespace std; +using std::string; +using namespace boost; +using namespace boost::python; +using namespace Imath; +using namespace IECore; + +namespace IECorePython +{ + +template +std::string repr( T &x ) +{ + std::stringstream s; + + s << "IECore." << x.typeName() << "( "; + + object item( x.readable() ); + + assert( item.attr( "__repr__" ) != object() ); + + s << call_method< std::string >( item.ptr(), "__repr__" ); + + s << " )"; + + return s.str(); +} + +template +static void setValue( T &that, const typename T::ValueType &v ) +{ + that.writable() = v; +} + +template +static typename T::ValueType &getValue( T &that ) +{ + return that.writable(); +} + +template< typename T > +void bindRampData() +{ + TypedDataFromType(); + + RunTimeTypedClass() + .def( init<>() ) + .def( init() ) + .add_property( "value", make_function( &getValue, return_internal_reference<>() ), &setValue ) + .def( "__repr__", &repr ) + .def( "hasBase", &T::hasBase ).staticmethod( "hasBase" ) + ; +} + +void bindRampData() +{ + bindRampData(); + bindRampData(); + bindRampData(); +} + +} // namespace IECorePython diff --git a/src/IECorePython/TypeIdBinding.cpp b/src/IECorePython/TypeIdBinding.cpp index f9120eaecf..a286043e7f 100644 --- a/src/IECorePython/TypeIdBinding.cpp +++ b/src/IECorePython/TypeIdBinding.cpp @@ -233,6 +233,9 @@ void bindTypeId() .value( "SplineddData", SplineddDataTypeId ) .value( "SplinefColor3fData", SplinefColor3fDataTypeId ) .value( "SplinefColor4fData", SplinefColor4fDataTypeId ) + .value( "RampffDataTypeId", RampffDataTypeId ) + .value( "RampfColor3fDataTypeId", RampfColor3fDataTypeId ) + .value( "RampfColor4fDataTypeId", RampfColor4fDataTypeId ) .value( "SplineffParameter", SplineffParameterTypeId ) .value( "SplineddParameter", SplineddParameterTypeId ) .value( "SplinefColor3fParameter", SplinefColor3fParameterTypeId ) @@ -243,10 +246,6 @@ void bindTypeId() .value( "ObjectVectorParameter", ObjectVectorParameterTypeId ) .value( "DateTimeData", DateTimeDataTypeId ) .value( "DateTimeParameter", DateTimeParameterTypeId ) - .value( "TimeDurationData", TimeDurationDataTypeId ) - .value( "TimeDurationParameter", TimeDurationParameterTypeId ) - .value( "TimePeriodData", TimePeriodDataTypeId ) - .value( "TimePeriodParameter", TimePeriodParameterTypeId ) .value( "FrameList", FrameListTypeId ) .value( "EmptyFrameList", EmptyFrameListTypeId ) .value( "FrameRange", FrameRangeTypeId ) diff --git a/src/IECorePythonModule/IECore.cpp b/src/IECorePythonModule/IECore.cpp index 844b92d636..a7c02308ec 100644 --- a/src/IECorePythonModule/IECore.cpp +++ b/src/IECorePythonModule/IECore.cpp @@ -114,6 +114,8 @@ #include "IECorePython/AngleConversionBinding.h" #include "IECorePython/SplineBinding.h" #include "IECorePython/SplineDataBinding.h" +#include "IECorePython/RampBinding.h" +#include "IECorePython/RampDataBinding.h" #include "IECorePython/ObjectVectorBinding.h" #include "IECorePython/HenyeyGreensteinBinding.h" #include "IECorePython/OversamplesCalculatorBinding.h" @@ -263,6 +265,8 @@ BOOST_PYTHON_MODULE(_IECore) bindAngleConversion(); bindSpline(); bindSplineData(); + bindRamp(); + bindRampData(); bindObjectVector(); bindHenyeyGreenstein(); bindDateTimeData(); diff --git a/src/IECoreScene/SceneCache.cpp b/src/IECoreScene/SceneCache.cpp index 2c60296eb0..37a22ed4aa 100644 --- a/src/IECoreScene/SceneCache.cpp +++ b/src/IECoreScene/SceneCache.cpp @@ -47,6 +47,7 @@ #include "IECore/MessageHandler.h" #include "IECore/ObjectInterpolator.h" #include "IECore/SimpleTypedData.h" +#include "IECore/SplineData.h" #include "IECore/TransformationMatrixData.h" #include "IECore/PathMatcherData.h" @@ -1156,6 +1157,11 @@ class SceneCache::ReaderImplementation : public SceneCache::Implementation } } + if( ShaderNetwork *shaderNetwork = runTimeCast( result.get() ) ) + { + ShaderNetworkAlgo::convertDeprecatedSplines( shaderNetwork ); + } + return result; } diff --git a/src/IECoreScene/ShaderNetworkAlgo.cpp b/src/IECoreScene/ShaderNetworkAlgo.cpp index bab0cecf0e..5284ab64cb 100644 --- a/src/IECoreScene/ShaderNetworkAlgo.cpp +++ b/src/IECoreScene/ShaderNetworkAlgo.cpp @@ -39,7 +39,7 @@ #include "IECore/DataAlgo.h" #include "IECore/SimpleTypedData.h" #include "IECore/StringAlgo.h" -#include "IECore/SplineData.h" +#include "IECore/RampData.h" #include "IECore/TypeTraits.h" #include "IECore/VectorTypedData.h" #include "IECore/MessageHandler.h" @@ -269,6 +269,12 @@ const bool g_defaultAdapterRegistrations = [] () { return true; } (); +const InternedString &componentConnectionAdapterLabel() +{ + static InternedString ret( "cortex_autoAdapter" ); + return ret; +} + bool isSplitAdapter( const Shader *shader, InternedString &component, InternedString &inParameter, InternedString &outParameter ) { if( auto *d = shader->blindData()->member( g_splitAdapterComponent ) ) @@ -283,7 +289,7 @@ bool isSplitAdapter( const Shader *shader, InternedString &component, InternedSt } return true; } - else if( auto *b = shader->blindData()->member( ShaderNetworkAlgo::componentConnectionAdapterLabel() ) ) + else if( auto *b = shader->blindData()->member( componentConnectionAdapterLabel() ) ) { // Legacy format. if( b->readable() && shader->getName() == "MaterialX/mx_swizzle_color_float" ) @@ -313,7 +319,7 @@ bool isJoinAdapter( const Shader *shader, std::array &inParam return true; } } - else if( auto *b = shader->blindData()->member( ShaderNetworkAlgo::componentConnectionAdapterLabel() ) ) + else if( auto *b = shader->blindData()->member( componentConnectionAdapterLabel() ) ) { // Legacy format. if( b->readable() && shader->getName() == "MaterialX/mx_pack_color" ) @@ -590,12 +596,6 @@ void ShaderNetworkAlgo::deregisterJoinAdapter( const std::string &destinationSha joinAdapters()[destinationShaderType].erase( destinationParameterType ); } -const InternedString &ShaderNetworkAlgo::componentConnectionAdapterLabel() -{ - static InternedString ret( "cortex_autoAdapter" ); - return ret; -} - ////////////////////////////////////////////////////////////////////////// // OSL Utilities ////////////////////////////////////////////////////////////////////////// @@ -624,20 +624,13 @@ ShaderNetwork::Parameter convertComponentSuffix( const ShaderNetwork::Parameter ); } -} // namespace - -void ShaderNetworkAlgo::convertOSLComponentConnections( ShaderNetwork *network ) -{ - convertOSLComponentConnections( network, 10900 /* OSL 1.9 */ ); -} - -void ShaderNetworkAlgo::convertOSLComponentConnections( ShaderNetwork *network, int oslVersion ) +void convertOSLComponentConnections( ShaderNetwork *network, int oslVersion ) { if( oslVersion < 11000 ) { // OSL doesn't support component-level connections, // so we emulate them by inserting conversion shaders for OSL nodes. - addComponentConnectionAdapters( network, "osl:" ); + ShaderNetworkAlgo::addComponentConnectionAdapters( network, "osl:" ); return; } @@ -681,9 +674,12 @@ void ShaderNetworkAlgo::convertOSLComponentConnections( ShaderNetwork *network, } } +} // namespace + + void ShaderNetworkAlgo::convertToOSLConventions( ShaderNetwork *network, int oslVersion ) { - expandSplines( network, "osl:" ); + expandRamps( network, "osl:" ); // \todo - it would be a bit more efficient to integrate this, and only traverse the network once, // but I don't think it's worth duplicated the code - fix this up once this call is standard and we @@ -781,7 +777,7 @@ ShaderNetworkPtr ShaderNetworkAlgo::convertObjectVector( const ObjectVector *net } ////////////////////////////////////////////////////////////////////////// -// Spline handling +// Ramp handling ////////////////////////////////////////////////////////////////////////// namespace @@ -792,28 +788,8 @@ std::string_view stringViewFromMatch( const std::string &s, const boost::smatch return std::string_view( s.data() + match.position( index ), match.length( index ) ); } -template -std::pair< size_t, size_t > getEndPointDuplication( const T &basis ) -{ - if( basis == T::linear() ) - { - // OSL discards the first and last segment of linear curves - // "To maintain consistency with the other spline types" - // so we need to duplicate the end points to preserve all provided segments - return std::make_pair( 1, 1 ); - } - else if( basis == T::constant() ) - { - // Also, "To maintain consistency", "constant splines ignore the first and the two last - // data values." - return std::make_pair( 1, 2 ); - } - - 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 ) +lookupRampParameterSuffixes( 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. @@ -840,65 +816,26 @@ lookupSplinePlugSuffixes( const std::string &shaderName ) } } -template -void expandSpline( const InternedString &name, const Spline &spline, CompoundDataMap &newParameters, const std::string &shaderName ) +template +int expandRamp( const InternedString &name, const Ramp &ramp, CompoundDataMap &newParameters, const std::string &shaderName ) { - const char *basis = "catmull-rom"; - if( spline.basis == Spline::Basis::bezier() ) - { - basis = "bezier"; - } - else if( spline.basis == Spline::Basis::bSpline() ) - { - basis = "bspline"; - } - else if( spline.basis == Spline::Basis::linear() ) - { - basis = "linear"; - } - else if( spline.basis == Spline::Basis::constant() ) - { - // Also, "To maintain consistency", "constant splines ignore the first and the two last - // data values." - basis = "constant"; - } - auto [ duplicateStartPoints, duplicateEndPoints ] = getEndPointDuplication( spline.basis ); + StringDataPtr basisData = new StringData(); + std::string &basis = basisData->writable(); - typedef TypedData< vector > XTypedVectorData; + typedef TypedData< vector > XTypedVectorData; typename XTypedVectorData::Ptr positionsData = new XTypedVectorData(); auto &positions = positionsData->writable(); - positions.reserve( spline.points.size() ); - typedef TypedData< vector > YTypedVectorData; + positions.reserve( ramp.points.size() ); + typedef TypedData< vector > YTypedVectorData; typename YTypedVectorData::Ptr valuesData = new YTypedVectorData(); auto &values = valuesData->writable(); - values.reserve( spline.points.size() + duplicateStartPoints + duplicateEndPoints ); - if( spline.points.size() ) - { - for( size_t i = 0; i < duplicateStartPoints; i++ ) - { - positions.push_back( spline.points.begin()->first ); - values.push_back( spline.points.begin()->second ); - } - } - for( typename Spline::PointContainer::const_iterator it = spline.points.begin(), eIt = spline.points.end(); it != eIt; ++it ) - { - positions.push_back( it->first ); - values.push_back( it->second ); - } - if( spline.points.size() ) - { - for( size_t i = 0; i < duplicateEndPoints; i++ ) - { - positions.push_back( spline.points.rbegin()->first ); - values.push_back( spline.points.rbegin()->second ); - } - } + ramp.toOSL( basis, positions, values ); - auto [ positionsSuffix, floatValuesSuffix, colorValuesSuffix, basisSuffix, countSuffix ] = lookupSplinePlugSuffixes( shaderName ); + auto [ positionsSuffix, floatValuesSuffix, colorValuesSuffix, basisSuffix, countSuffix ] = lookupRampParameterSuffixes( shaderName ); newParameters[ name.string() + *positionsSuffix ] = positionsData; - if constexpr( std::is_same_v< typename Spline::YType, float > ) + if constexpr( std::is_same_v< typename Ramp::YType, float > ) { newParameters[ name.string() + *floatValuesSuffix ] = valuesData; } @@ -906,70 +843,14 @@ void expandSpline( const InternedString &name, const Spline &spline, CompoundDat { newParameters[ name.string() + *colorValuesSuffix ] = valuesData; } - newParameters[ name.string() + *basisSuffix ] = new StringData( basis ); + newParameters[ name.string() + *basisSuffix ] = basisData; if( countSuffix ) { newParameters[ name.string() + *countSuffix ] = new IntData( positionsData->readable().size() ); } -} - -template -IECore::DataPtr loadSpline( - const StringData *basisData, - const IECore::TypedData< std::vector< typename SplineData::ValueType::XType > > *positionsData, - const IECore::TypedData< std::vector< typename SplineData::ValueType::YType > > *valuesData -) -{ - typename SplineData::Ptr resultData = new SplineData(); - auto &result = resultData->writable(); - - size_t unduplicateStartPoints = 0; - size_t unduplicateEndPoints = 0; - - const std::string &basis = basisData->readable(); - if( basis == "bezier" ) - { - result.basis = SplineData::ValueType::Basis::bezier(); - } - if( basis == "bspline" ) - { - result.basis = SplineData::ValueType::Basis::bSpline(); - } - else if( basis == "linear" ) - { - // Reverse the duplication we do when expanding splines - unduplicateStartPoints = 1; - unduplicateEndPoints = 1; - result.basis = SplineData::ValueType::Basis::linear(); - } - else if( basis == "constant" ) - { - // Reverse the duplication we do when expanding splines - unduplicateStartPoints = 1; - unduplicateEndPoints = 2; - result.basis = SplineData::ValueType::Basis::constant(); - } - else - { - result.basis = SplineData::ValueType::Basis::catmullRom(); - } - - const auto &positions = positionsData->readable(); - const auto &values = valuesData->readable(); - - size_t n = std::min( positions.size(), values.size() ); - for( size_t i = 0; i < n; ++i ) - { - if( i < unduplicateStartPoints || i >= n - unduplicateEndPoints ) - { - continue; - } - - result.points.insert( typename SplineData::ValueType::Point( positions[i], values[i] ) ); - } - return resultData; + return positionsData->readable().size(); } void ensureParametersCopy( @@ -985,10 +866,10 @@ void ensureParametersCopy( } } -IECore::ConstCompoundDataPtr collapseSplineParametersInternal( const IECore::ConstCompoundDataPtr ¶metersData, const std::string &shaderName ) +IECore::ConstCompoundDataPtr collapseRampParametersInternal( const IECore::ConstCompoundDataPtr ¶metersData, const std::string &shaderName ) { - auto [ positionsSuffix, floatValuesSuffix, colorValuesSuffix, basisSuffix, countSuffix ] = lookupSplinePlugSuffixes( shaderName ); + auto [ positionsSuffix, floatValuesSuffix, colorValuesSuffix, basisSuffix, countSuffix ] = lookupRampParameterSuffixes( shaderName ); const CompoundDataMap ¶meters( parametersData->readable() ); CompoundDataPtr newParametersData; @@ -1043,26 +924,38 @@ IECore::ConstCompoundDataPtr collapseSplineParametersInternal( const IECore::Con } IECore::InternedString valuesName = prefix + *floatValuesSuffix; - IECore::DataPtr foundSpline; + IECore::DataPtr foundRamp; if( const FloatVectorData *floatValues = parametersData->member( valuesName ) ) { - foundSpline = loadSpline( basis, floatPositions, floatValues ); + RampffData::Ptr rampData = new RampffData(); + rampData->writable().fromOSL( + basis->readable(), floatPositions->readable(), floatValues->readable(), prefix + ); + foundRamp = rampData; } else { valuesName = prefix + *colorValuesSuffix; if( const Color3fVectorData *color3Values = parametersData->member( valuesName ) ) { - foundSpline = loadSpline( basis, floatPositions, color3Values ); + RampfColor3fData::Ptr rampData = new RampfColor3fData(); + rampData->writable().fromOSL( + basis->readable(), floatPositions->readable(), color3Values->readable(), prefix + ); + foundRamp = rampData; } else if( const Color4fVectorData *color4Values = parametersData->member( valuesName ) ) { - foundSpline = loadSpline( basis, floatPositions, color4Values ); + RampfColor4fData::Ptr rampData = new RampfColor4fData(); + rampData->writable().fromOSL( + basis->readable(), floatPositions->readable(), color4Values->readable(), prefix + ); + foundRamp = rampData; } } - if( foundSpline ) + if( foundRamp ) { ensureParametersCopy( parameters, newParametersData, newParameters ); newParameters->erase( maybeBasis.first ); @@ -1073,7 +966,7 @@ IECore::ConstCompoundDataPtr collapseSplineParametersInternal( const IECore::Con newParameters->erase( countName ); } - (*newParameters)[prefix] = foundSpline; + (*newParameters)[prefix] = foundRamp; } } @@ -1107,27 +1000,35 @@ const InternedString g_arrayOutputNames[maxArrayInputAdapterSize + 1] = { "out30", "out31", "out32" }; -const boost::regex g_splineElementRegex( "^(.*)\\[(.*)\\]\\.y(.*)$" ); +const boost::regex g_rampElementRegex( "^(.*)\\[(.*)\\]\\.y(.*)$" ); const boost::regex g_splineAdapterInRegex( "^in([0-9]+)(\\..*)?$" ); -template< typename TypedSpline > -std::pair< InternedString, int > createSplineInputAdapter( - ShaderNetwork *network, const TypedData *splineData, - const IECore::CompoundDataMap &newParameters, const IECore::InternedString &splineParameterName, +struct RampInputAdapterParameters +{ + InternedString adapterHandle; + size_t origSize; + size_t expandedSize; + int knotOffset; +}; + +template< typename TypedRamp > +RampInputAdapterParameters createRampInputAdapter( + ShaderNetwork *network, const TypedData *rampData, + const IECore::CompoundDataMap &newParameters, const IECore::InternedString &rampParameterName, const ShaderNetwork::Parameter &destination ) { - using ValueVectorData = TypedData< std::vector< typename TypedSpline::YType > >; + using ValueVectorData = TypedData< std::vector< typename TypedRamp::YType > >; - IECore::InternedString splineValuesName = splineParameterName.string() + "Values"; + IECore::InternedString splineValuesName = rampParameterName.string() + "Values"; auto findValues = newParameters.find( splineValuesName ); const ValueVectorData *splineValuesData = findValues != newParameters.end() ? runTimeCast( findValues->second.get() ) : nullptr; if( !splineValuesData ) { - throw IECore::Exception( "Internal failure in convertToOSLConventions - expandSpline did not create values." ); + throw IECore::Exception( "Internal failure in convertToOSLConventions - expandRamp did not create values." ); } - const std::vector< typename TypedSpline::YType > &splineValues = splineValuesData->readable(); + const std::vector< typename TypedRamp::YType > &splineValues = splineValuesData->readable(); if( splineValues.size() > maxArrayInputAdapterSize ) { @@ -1141,30 +1042,30 @@ std::pair< InternedString, int > createSplineInputAdapter( // Using this adapter depends on Gaffer being available, but I guess we don't really // care about use cases outside Gaffer ( and in terms of using exported USD elsewhere, - // this spline representation is only used in Gaffer's spline shaders, so it's not very + // this ramp representation is only used in Gaffer's ramp shaders, so it's not very // useful if you don't have access to Gaffer shaders anyway ). ShaderPtr adapter = new Shader( - std::is_same< TypedSpline, SplinefColor3f >::value ? g_colorToArrayAdapter : g_floatToArrayAdapter, + std::is_same< TypedRamp, RampfColor3f >::value ? g_colorToArrayAdapter : g_floatToArrayAdapter, g_oslShader ); for( unsigned int i = 0; i < splineValues.size(); i++ ) { - adapter->parameters()[ g_arrayInputNames[i] ] = new TypedData< typename TypedSpline::YType >( splineValues[i] ); + adapter->parameters()[ g_arrayInputNames[i] ] = new TypedData< typename TypedRamp::YType >( splineValues[i] ); } - InternedString adapterHandle = network->addShader( destination.shader.string() + "_" + splineParameterName.string() + "InputArrayAdapter", std::move( adapter ) ); + InternedString adapterHandle = network->addShader( destination.shader.string() + "_" + rampParameterName.string() + "InputArrayAdapter", std::move( adapter ) ); network->addConnection( ShaderNetwork::Connection( { adapterHandle, g_arrayOutputNames[ splineValues.size() ] }, { destination.shader, splineValuesName } ) ); - return std::make_pair( adapterHandle, getEndPointDuplication( splineData->readable().basis ).first ); + return { adapterHandle, rampData->readable().points.size(), splineValues.size(), rampData->readable().oslStartPointMultiplicity() }; } } // namespace -void ShaderNetworkAlgo::collapseSplines( ShaderNetwork *network, std::string targetPrefix ) +void ShaderNetworkAlgo::collapseRamps( ShaderNetwork *network, std::string targetPrefix ) { std::vector< IECore::InternedString > adapters; @@ -1175,23 +1076,23 @@ void ShaderNetworkAlgo::collapseSplines( ShaderNetwork *network, std::string tar continue; } - bool isSplineAdapter = shader->getType() == g_oslShader && ( + bool isRampAdapter = shader->getType() == g_oslShader && ( shader->getName() == g_colorToArrayAdapter || shader->getName() == g_floatToArrayAdapter ); - if( isSplineAdapter ) + if( isRampAdapter ) { adapters.push_back( name ); continue; } - // For nodes which aren't spline adapters, we just need to deal with any parameters that are splines - ConstCompoundDataPtr collapsed = collapseSplineParametersInternal( shader->parametersData(), shader->getName() ); + // For nodes which aren't spline adapters, we just need to deal with any parameters that can become ramps + ConstCompoundDataPtr collapsed = collapseRampParametersInternal( shader->parametersData(), shader->getName() ); if( collapsed != shader->parametersData() ) { - // \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, collapseSplineParametersInternal could + // \todo - this const_cast is ugly, although safe because if the return from collapseRampParameterInternals + // doesn't match the input, it is freshly allocated. Now that collapseRampParameters is fully + // deprecated, and no longer visible publicly, collapseRampParametersInternal 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() ) ) ) ); } @@ -1207,12 +1108,12 @@ void ShaderNetworkAlgo::collapseSplines( ShaderNetwork *network, std::string tar if( !boost::ends_with( splineValuesName, "Values" ) ) { IECore::msg( - Msg::Error, "ShaderNetworkAlgo", "Invalid spline plug name \"" + splineValuesName + "\"" + Msg::Error, "ShaderNetworkAlgo", "Invalid spline parameter name \"" + splineValuesName + "\"" ); continue; } - InternedString splineName = string_view( splineValuesName ).substr( 0, splineValuesName.size() - 6 ); + InternedString rampName = string_view( splineValuesName ).substr( 0, splineValuesName.size() - 6 ); const IECoreScene::Shader *targetShader = network->getShader( output.destination.shader ); if( !targetShader ) @@ -1223,25 +1124,29 @@ void ShaderNetworkAlgo::collapseSplines( ShaderNetwork *network, std::string tar } const IECore::CompoundDataMap &targetParameters = targetShader->parameters(); - int targetSplineKnotOffset = -1; - auto targetParameterIt = targetParameters.find( splineName ); + int targetRampKnotOffset = -1; + int targetRampSize = -1; + auto targetParameterIt = targetParameters.find( rampName ); if( targetParameterIt != targetParameters.end() ) { - if( const SplineffData *findSplineff = runTimeCast( targetParameterIt->second.get() ) ) + if( const RampffData *findRampff = runTimeCast( targetParameterIt->second.get() ) ) { - targetSplineKnotOffset = getEndPointDuplication( findSplineff->readable().basis ).first; + targetRampKnotOffset = findRampff->readable().oslStartPointMultiplicity(); + targetRampSize = findRampff->readable().points.size(); } - else if( const SplinefColor3fData *findSplinefColor3f = runTimeCast( targetParameterIt->second.get() ) ) + else if( const RampfColor3fData *findRampfColor3f = runTimeCast( targetParameterIt->second.get() ) ) { - targetSplineKnotOffset = getEndPointDuplication( findSplinefColor3f->readable().basis ).first; + targetRampKnotOffset = findRampfColor3f->readable().oslStartPointMultiplicity(); + targetRampSize = findRampfColor3f->readable().points.size(); } - else if( const SplinefColor4fData *findSplinefColor4f = runTimeCast( targetParameterIt->second.get() ) ) + else if( const RampfColor4fData *findRampfColor4f = runTimeCast( targetParameterIt->second.get() ) ) { - targetSplineKnotOffset = getEndPointDuplication( findSplinefColor4f->readable().basis ).first; + targetRampKnotOffset = findRampfColor4f->readable().oslStartPointMultiplicity(); + targetRampSize = findRampfColor4f->readable().points.size(); } } - if( targetSplineKnotOffset == -1 ) + if( targetRampKnotOffset == -1 ) { IECore::msg( Msg::Error, "ShaderNetworkAlgo", @@ -1266,20 +1171,27 @@ void ShaderNetworkAlgo::collapseSplines( ShaderNetwork *network, std::string tar int elementId = StringAlgo::toInt( stringViewFromMatch( adapterDestName, match, 1 ) ) - - targetSplineKnotOffset; + - targetRampKnotOffset; + + if( elementId < 0 || elementId >= targetRampSize ) + { + // The likely cause of elements that don't map to the collapsed ramp is that this connection + // was created to handle endpoint duplication. + continue; + } InternedString origDestName; if( match[2].matched ) { origDestName = StringAlgo::concat( - splineName.string(), "[", std::to_string( elementId ), "].y", + rampName.string(), "[", std::to_string( elementId ), "].y", stringViewFromMatch( adapterDestName, match, 2 ) ); } else { origDestName = StringAlgo::concat( - splineName.string(), "[", std::to_string( elementId ), "].y" + rampName.string(), "[", std::to_string( elementId ), "].y" ); } @@ -1293,7 +1205,7 @@ void ShaderNetworkAlgo::collapseSplines( ShaderNetwork *network, std::string tar } } -void ShaderNetworkAlgo::expandSplines( ShaderNetwork *network, std::string targetPrefix ) +void ShaderNetworkAlgo::expandRamps( ShaderNetwork *network, std::string targetPrefix ) { for( const auto &s : network->shaders() ) { @@ -1309,32 +1221,32 @@ void ShaderNetworkAlgo::expandSplines( ShaderNetwork *network, std::string targe for( const auto &[name, value] : origParameters ) { - if( const SplinefColor3fData *colorSpline = runTimeCast( value.get() ) ) + if( const RampfColor3fData *colorRamp = runTimeCast( value.get() ) ) { ensureParametersCopy( origParameters, newParametersData, newParameters ); newParameters->erase( name ); - expandSpline( name, colorSpline->readable(), *newParameters, s.second->getName() ); + expandRamp( name, colorRamp->readable(), *newParameters, s.second->getName() ); } - else if( const SplineffData *floatSpline = runTimeCast( value.get() ) ) + else if( const RampffData *floatRamp = runTimeCast( value.get() ) ) { ensureParametersCopy( origParameters, newParametersData, newParameters ); newParameters->erase( name ); - expandSpline( name, floatSpline->readable(), *newParameters, s.second->getName() ); + expandRamp( name, floatRamp->readable(), *newParameters, s.second->getName() ); } } if( !newParameters ) { - // No splines to convert + // No ramps to convert continue; } - // currentSplineArrayAdapters holds array adapters that we need to use to hook up inputs to - // spline plugs. It is indexed by the name of a spline parameter for the shader, and holds - // the name of the adapter shader, and the offset we need to use when accessing the knot - // vector. + // currentRampArrayAdapters holds array adapters that we need to use to hook up inputs to + // spline parameters that were converted from ramp. It is indexed by the name of a ramp + // parameter for the shader, and holds the name of the adapter shader, and the offset we + // need to use when accessing the knot vector. - std::map< IECore::InternedString, std::pair< IECore::InternedString, size_t > > currentSplineArrayAdapters; + std::map< IECore::InternedString, RampInputAdapterParameters > currentRampArrayAdapters; std::vector< ShaderNetwork::Connection > connectionsToAdd; ShaderNetwork::ConnectionRange inputConnections = network->inputConnections( s.first ); @@ -1345,89 +1257,100 @@ void ShaderNetworkAlgo::expandSplines( ShaderNetwork *network, std::string targe const ShaderNetwork::Connection connection = *it++; const std::string &destName = connection.destination.name.string(); - boost::smatch splineElementMatch; - if( !boost::regex_match( destName, splineElementMatch, g_splineElementRegex) ) + boost::smatch rampElementMatch; + if( !boost::regex_match( destName, rampElementMatch, g_rampElementRegex) ) { continue; } - IECore::InternedString parameterName( stringViewFromMatch( destName, splineElementMatch, 1 ) ); + IECore::InternedString parameterName( stringViewFromMatch( destName, rampElementMatch, 1 ) ); auto findParameter = origParameters.find( parameterName ); if( findParameter == origParameters.end() ) { continue; } - const SplinefColor3fData* colorSplineData = runTimeCast( findParameter->second.get() ); - const SplineffData* floatSplineData = runTimeCast( findParameter->second.get() ); + const RampfColor3fData* colorRampData = runTimeCast( findParameter->second.get() ); + const RampffData* floatRampData = runTimeCast( findParameter->second.get() ); - if( !( colorSplineData || floatSplineData ) ) + if( !( colorRampData || floatRampData ) ) { continue; } - int numPoints = colorSplineData ? colorSplineData->readable().points.size() : floatSplineData->readable().points.size(); - // Insert a conversion shader to handle connection to component - auto [ adapterIter, newlyInserted ] = currentSplineArrayAdapters.insert( { parameterName, std::make_pair( IECore::InternedString(), 0 ) } ); + auto [ adapterIter, newlyInserted ] = currentRampArrayAdapters.insert( { parameterName, RampInputAdapterParameters() } ); if( newlyInserted ) { - if( colorSplineData ) + if( colorRampData ) { - adapterIter->second = createSplineInputAdapter( - network, colorSplineData, *newParameters, parameterName, connection.destination + adapterIter->second = createRampInputAdapter( + network, colorRampData, *newParameters, parameterName, connection.destination ); } else { - adapterIter->second = createSplineInputAdapter( - network, floatSplineData, *newParameters, parameterName, connection.destination + adapterIter->second = createRampInputAdapter( + network, floatRampData, *newParameters, parameterName, connection.destination ); } } - const auto [ adapterHandle, knotOffset ] = adapterIter->second; + const RampInputAdapterParameters &adapterParms = adapterIter->second; int elementId; - std::string_view elementIdString( stringViewFromMatch( destName, splineElementMatch, 2 ) ); + std::string_view elementIdString( stringViewFromMatch( destName, rampElementMatch, 2 ) ); try { elementId = StringAlgo::toInt( elementIdString ); } catch( ... ) { - throw IECore::Exception( StringAlgo::concat( "Invalid spline point index ", elementIdString ) ); + throw IECore::Exception( StringAlgo::concat( "Invalid ramp point index ", elementIdString ) ); } - if( elementId < 0 || elementId >= numPoints ) + if( elementId < 0 || elementId >= (int)adapterParms.origSize ) { - throw IECore::Exception( "Spline index " + std::to_string( elementId ) + " is out of range in spline with " + std::to_string( numPoints ) + " points." ); + throw IECore::Exception( "Connection to ramp index " + std::to_string( elementId ) + " is out of range in ramp with " + std::to_string( adapterParms.origSize ) + " points." ); } - // We form only a single connection, even if we are at an endpoint which is duplicated during - // expandSpline. This is OK because the end points that are duplicated by expandSpline are ignored - // by OSL. - // - // An aside : the X values of the ignored points do need to be non-decreasing sometimes. There are - // two contraditory claims in the OSL spec, that: - // "Results are undefined if the knots ... not ... monotonic" - // and - // "constant splines ignore the first and the two last data values." - // This statements combine to make it ambiguous whether the duplicated value is completely - // ignored, or whether it must be monotonic ... in practice, it seems to cause problems for - // constant, but not linear interpolation. - // - // In any case, we only make connections to the Y value, so there is no problem with ignoring - // the duplicated values - - InternedString destinationName = g_arrayInputNames[elementId + knotOffset]; - if( splineElementMatch.length( 3 ) ) + // Map connections to the corresponding parameters of the expanded ramp. When mapping the + // first or last point, the value may need to be connected multiple times to match the end + // point duplication. This is needed in order to actually reach the end point value when using + // BSpline or CatmullRom interpolation. It doesn't actually matter for Linear or Constant, which + // have duplicated end points that aren't used, just because OSL thought it would be good idea to + // specify unused duplicated end points for "consistency", but for simplicity, we always connect + // the first or last control point to the duplicated end points. + + network->removeConnection( connection ); + + int outIndexMin, outIndexMax; + + if( elementId == 0 ) + { + outIndexMin = 0; + outIndexMax = adapterParms.knotOffset; + } + else if( elementId == (int)( adapterParms.origSize - 1 ) ) + { + outIndexMin = elementId + adapterParms.knotOffset; + outIndexMax = adapterParms.expandedSize - 1; + } + else { - destinationName = StringAlgo::concat( destinationName.string(), stringViewFromMatch( destName, splineElementMatch, 3 ) ); + outIndexMin = outIndexMax = elementId + adapterParms.knotOffset; } - network->removeConnection( connection ); - network->addConnection( { connection.source, { adapterHandle, destinationName } } ); + for( int i = outIndexMin; i <= outIndexMax; i++ ) + { + InternedString destinationName = g_arrayInputNames[i]; + if( rampElementMatch.length( 3 ) ) + { + destinationName = StringAlgo::concat( destinationName.string(), stringViewFromMatch( destName, rampElementMatch, 3 ) ); + } + + network->addConnection( { connection.source, { adapterParms.adapterHandle, destinationName } } ); + } } network->setShader( s.first, std::move( new Shader( s.second->getName(), s.second->getType(), newParametersData.get() ) ) ); @@ -1435,40 +1358,39 @@ void ShaderNetworkAlgo::expandSplines( ShaderNetwork *network, std::string targe } } -IECore::ConstCompoundDataPtr ShaderNetworkAlgo::collapseSplineParameters( const IECore::ConstCompoundDataPtr ¶metersData ) +void ShaderNetworkAlgo::convertDeprecatedSplines( ShaderNetwork *network ) { - return collapseSplineParametersInternal( parametersData, "" ); -} - -IECore::ConstCompoundDataPtr ShaderNetworkAlgo::expandSplineParameters( const IECore::ConstCompoundDataPtr ¶metersData ) -{ - const CompoundDataMap ¶meters( parametersData->readable() ); + for( const auto &s : network->shaders() ) + { + const CompoundDataMap &origParameters = s.second->parameters(); - CompoundDataPtr newParametersData; - CompoundDataMap *newParameters = nullptr; + CompoundDataPtr newParametersData; + CompoundDataMap *newParameters = nullptr; - for( const auto &i : parameters ) - { - if( const SplinefColor3fData *colorSpline = runTimeCast( i.second.get() ) ) + for( const auto &[name, value] : origParameters ) { - ensureParametersCopy( parameters, newParametersData, newParameters ); - newParameters->erase( i.first ); - expandSpline( i.first, colorSpline->readable(), *newParameters, "" ); + if( const SplinefColor3fData *colorSpline = runTimeCast( value.get() ) ) + { + ensureParametersCopy( origParameters, newParametersData, newParameters ); + RampfColor3fDataPtr rampData = new RampfColor3fData; + rampData->writable().fromDeprecatedSpline( colorSpline->readable() ); + (*newParameters)[ name ] = rampData; + } + else if( const SplineffData *floatSpline = runTimeCast( value.get() ) ) + { + ensureParametersCopy( origParameters, newParametersData, newParameters ); + RampffDataPtr rampData = new RampffData; + rampData->writable().fromDeprecatedSpline( floatSpline->readable() ); + (*newParameters)[ name ] = rampData; + } } - else if( const SplineffData *floatSpline = runTimeCast( i.second.get() ) ) + + if( newParameters ) { - ensureParametersCopy( parameters, newParametersData, newParameters ); - newParameters->erase( i.first ); - expandSpline( i.first, floatSpline->readable(), *newParameters, "" ); + network->setShader( s.first, std::move( + new Shader( s.second->getName(), s.second->getType(), newParametersData.get() ) + ) ); } - } - if( newParametersData ) - { - return newParametersData; - } - else - { - return parametersData; } } diff --git a/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp b/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp index c886c98a7d..41c439f350 100644 --- a/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp +++ b/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp @@ -65,26 +65,6 @@ void registerJoinAdapterWrapper( const std::string &destinationShaderType, IECor ShaderNetworkAlgo::registerJoinAdapter( destinationShaderType, destinationParameterType, adapter, inParameters, outParameter ); } -void convertOSLComponentConnectionsWrapper( ShaderNetwork *network, int oslVersion ) -{ - ShaderNetworkAlgo::convertOSLComponentConnections( network, oslVersion ); -} - -CompoundDataPtr collapseSplineParametersWrapper( CompoundDataPtr parameters ) -{ - return boost::const_pointer_cast< CompoundData >( ShaderNetworkAlgo::collapseSplineParameters( parameters ) ); -} - -CompoundDataPtr expandSplineParametersWrapper( CompoundDataPtr parameters ) -{ - return boost::const_pointer_cast< CompoundData >( ShaderNetworkAlgo::expandSplineParameters( parameters ) ); -} - -std::string componentConnectionAdapterLabelWrapper() -{ - return ShaderNetworkAlgo::componentConnectionAdapterLabel().string(); -} - } // namespace void IECoreSceneModule::bindShaderNetworkAlgo() @@ -101,13 +81,9 @@ void IECoreSceneModule::bindShaderNetworkAlgo() def( "deregisterSplitAdapter", &ShaderNetworkAlgo::deregisterSplitAdapter, ( arg( "destinationShaderType" ), arg( "component" ) ) ); def( "registerJoinAdapter", ®isterJoinAdapterWrapper, ( arg( "destinationShaderType" ), arg( "destinationParameterType" ), arg( "adapter" ), arg( "inParameters" ), arg( "outParameter" ) ) ); def( "deregisterJoinAdapter", &ShaderNetworkAlgo::deregisterJoinAdapter, ( arg( "destinationShaderType" ), arg( "destinationParameterType" ) ) ); - def( "componentConnectionAdapterLabel", &componentConnectionAdapterLabelWrapper ); def( "convertToOSLConventions", &ShaderNetworkAlgo::convertToOSLConventions ); - def( "convertOSLComponentConnections", &convertOSLComponentConnectionsWrapper, ( arg( "network" ), arg( "oslVersion" ) = 10900 ) ); def( "convertObjectVector", &ShaderNetworkAlgo::convertObjectVector ); - def( "collapseSplines", &ShaderNetworkAlgo::collapseSplines, ( arg( "network" ), arg( "targetPrefix" ) = "" ) ); - def( "expandSplines", &ShaderNetworkAlgo::expandSplines, ( arg( "network" ), arg( "targetPrefix" ) = "" ) ); - def( "collapseSplineParameters", &collapseSplineParametersWrapper ); - def( "expandSplineParameters", &expandSplineParametersWrapper ); + def( "collapseRamps", &ShaderNetworkAlgo::collapseRamps, ( arg( "network" ), arg( "targetPrefix" ) = "" ) ); + def( "expandRamps", &ShaderNetworkAlgo::expandRamps, ( arg( "network" ), arg( "targetPrefix" ) = "" ) ); } diff --git a/test/IECore/All.py b/test/IECore/All.py index f0b2175445..42a10e4632 100644 --- a/test/IECore/All.py +++ b/test/IECore/All.py @@ -152,6 +152,8 @@ from PathMatcherDataTest import PathMatcherDataTest from CancellerTest import CancellerTest from ObjectMatrixTest import ObjectMatrixTest +from RampTest import * +from RampDataTest import * unittest.TestProgram( testRunner = unittest.TextTestRunner( diff --git a/test/IECore/RampDataTest.py b/test/IECore/RampDataTest.py new file mode 100644 index 0000000000..bf76cb2f63 --- /dev/null +++ b/test/IECore/RampDataTest.py @@ -0,0 +1,148 @@ +########################################################################## +# +# Copyright (c) 2025, Image Engine Design Inc. 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 Image Engine Design 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 os +import math +import unittest +import imath +import IECore + +class RampDataTest( unittest.TestCase ) : + + def testConstructor( self ) : + + d = IECore.RampffData() + self.assertEqual( d.value, IECore.Rampff() ) + + s = IECore.Rampff( ( ( 0, 0 ), ( 1, 1 ) ), IECore.RampInterpolation.MonotoneCubic ) + d = IECore.RampffData( s ) + + self.assertEqual( d.value, s ) + + def testValueAccess( self ) : + + d = IECore.RampffData() + d.value.interpolation = IECore.RampInterpolation.BSpline + + self.assertEqual( d.value.interpolation, IECore.RampInterpolation.BSpline ) + + d.value = IECore.Rampff( ( ( 0, 0 ), ( 1, 1 ) ), IECore.RampInterpolation.Linear ) + self.assertEqual( d.value, IECore.Rampff( ( ( 0, 0 ), ( 1, 1 ) ), IECore.RampInterpolation.Linear ) ) + + def testCopy( self ) : + + d = IECore.RampffData() + dd = d.copy() + + self.assertEqual( d, dd ) + + dd.value.interpolation = IECore.RampInterpolation.BSpline + + self.assertNotEqual( d, dd ) + + def testIO( self ) : + + s = IECore.Rampff( ( ( 0, 1 ), ( 1, 2 ), ( 2, 3 ), ( 3, 4 ) ), IECore.RampInterpolation.MonotoneCubic ) + + sd = IECore.RampffData( s ) + + IECore.ObjectWriter( sd, os.path.join( "test", "IECore", "RampData.cob" ) ).write() + + sdd = IECore.ObjectReader( os.path.join( "test", "IECore", "RampData.cob" ) ).read() + + self.assertEqual( sd, sdd ) + + def testColorIO( self ) : + + s = IECore.RampfColor3f( + ( ( 0, imath.Color3f(1) ), ( 1, imath.Color3f(2) ), ( 2, imath.Color3f(3) ), ( 3, imath.Color3f(4) ) ), + IECore.RampInterpolation.MonotoneCubic + ) + sd = IECore.RampfColor3fData( s ) + + IECore.ObjectWriter( sd, os.path.join( "test", "IECore", "RampData.cob" ) ).write() + + sdd = IECore.ObjectReader( os.path.join( "test", "IECore", "RampData.cob" ) ).read() + + self.assertEqual( sd, sdd ) + + def testRepr( self ) : + + s = IECore.RampfColor3f( + ( ( 0, imath.Color3f(1) ), ( 1, imath.Color3f(2) ), ( 2, imath.Color3f(3) ), ( 3, imath.Color3f(4) ) ), + IECore.RampInterpolation.BSpline + ) + + sd = IECore.RampfColor3fData( s ) + + self.assertEqual( repr(sd), "IECore.RampfColor3fData( " + repr(s) + " )" ) + + self.assertEqual( sd, eval( repr(sd) ) ) + + def testHash( self ) : + + points = ( ( 0, imath.Color3f(1) ), ( 1, imath.Color3f(2) ), ( 2, imath.Color3f(3) ), ( 3, imath.Color3f(4) ) ) + + s = IECore.RampfColor3fData( IECore.RampfColor3f( points, IECore.RampInterpolation.Linear ) ) + + h = s.hash() + + points = ( *points[:2], ( 3, imath.Color3f( 5 ) ) ) + s = IECore.RampfColor3fData( IECore.RampfColor3f( points, IECore.RampInterpolation.Linear ) ) + self.assertNotEqual( s.hash(), h ) + h = s.hash() + + points = points[:2] + s = IECore.RampfColor3fData( IECore.RampfColor3f( points, IECore.RampInterpolation.Linear ) ) + self.assertNotEqual( s.hash(), h ) + h = s.hash() + + s = IECore.RampfColor3fData( IECore.RampfColor3f( points, IECore.RampInterpolation.BSpline ) ) + self.assertNotEqual( s.hash(), h ) + h = s.hash() + + def setUp(self): + + if os.path.isfile( os.path.join( "test", "IECore", "RampData.cob" ) ) : + os.remove( os.path.join( "test", "IECore", "RampData.cob" ) ) + + def tearDown(self): + + if os.path.isfile( os.path.join( "test", "IECore", "RampData.cob" ) ) : + os.remove( os.path.join( "test", "IECore", "RampData.cob" ) ) + + +if __name__ == "__main__": + unittest.main() + diff --git a/test/IECore/RampTest.py b/test/IECore/RampTest.py new file mode 100644 index 0000000000..9efbeeb420 --- /dev/null +++ b/test/IECore/RampTest.py @@ -0,0 +1,120 @@ +########################################################################## +# +# Copyright (c) 2025, Image Engine Design Inc. 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 Image Engine Design 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 math +import unittest +import random +import imath +import IECore + +class RampTest( unittest.TestCase ) : + + def testConstructor( self ) : + + s = IECore.Rampff() + self.assertEqual( s.interpolation, IECore.RampInterpolation.CatmullRom ) + self.assertEqual( s.points(), () ) + + s = IECore.Rampff( (), IECore.RampInterpolation.MonotoneCubic ) + self.assertEqual( s.interpolation, IECore.RampInterpolation.MonotoneCubic ) + self.assertEqual( s.points(), () ) + + s = IECore.Rampff( ( ( 0, 1 ), ( 10, 2 ), ( 20, 0 ), ( 20, 0 ), ( 21, 2 ) ), IECore.RampInterpolation.BSpline ) + self.assertEqual( s.interpolation, IECore.RampInterpolation.BSpline ) + self.assertEqual( s.points(), ( ( 0, 1 ), ( 10, 2 ), ( 20, 0 ), ( 20, 0 ), ( 21, 2 ) ) ) + + def testRepr( self ) : + + s = IECore.Rampff( ( ( 0, 1 ), ( 10, 2 ), ( 20, 0 ), ( 20, 0 ), ( 21, 2 ) ), IECore.RampInterpolation.BSpline ) + + ss = eval( repr( s ) ) + self.assertEqual( s, ss ) + + s = IECore.RampfColor3f( ( ( 0, imath.Color3f( 1, 0, 0 ) ), ( 10, imath.Color3f( 0, 1, 0 ) ), ( 20, imath.Color3f( 1, 1, 0 ) ), ( 20, imath.Color3f( 0, 0, 1 ) ) ), IECore.RampInterpolation.Linear ) + + ss = eval( repr( s ) ) + self.assertEqual( s, ss ) + + def testEvaluator( self ) : + + s = IECore.Rampff( ( ( 0, 1 ), ( 1, 2 ), ( 2, 2 ), ( 3, 3 ) ), IECore.RampInterpolation.Linear ) + self.assertEqual( + s.evaluator(), + IECore.Splineff( IECore.CubicBasisf.linear(), ( ( 0, 1 ), ( 1, 2 ), ( 2, 2 ), ( 3, 3 ) ) ) + ) + + s = IECore.Rampff( ( ( 0, 1 ), ( 1, 2 ), ( 2, 2 ), ( 3, 3 ) ), IECore.RampInterpolation.CatmullRom ) + self.assertEqual( + s.evaluator(), + IECore.Splineff( IECore.CubicBasisf.catmullRom(), + ( ( 0, 1 ), ( 0, 1 ), ( 1, 2 ), ( 2, 2 ), ( 3, 3 ), ( 3, 3 ) ) + ) + ) + + s = IECore.RampfColor3f( + ( ( 0, imath.Color3f(1) ), ( 1, imath.Color3f(2) ), ( 2, imath.Color3f(2) ), ( 3, imath.Color3f(3) ) ), + IECore.RampInterpolation.Constant + ) + self.assertEqual( + s.evaluator(), + IECore.SplinefColor3f( IECore.CubicBasisf.constant(), + ( ( 0, imath.Color3f(1) ), ( 1, imath.Color3f(2) ), ( 2, imath.Color3f(2) ), ( 3, imath.Color3f(3) ) ) + ) + ) + + s = IECore.Rampff( ( ( 0, 1 ), ( 1, 2 ), ( 2, 2 ), ( 3, 3 ) ), IECore.RampInterpolation.BSpline ) + self.assertEqual( + s.evaluator(), + IECore.Splineff( IECore.CubicBasisf.bSpline(), + ( ( 0, 1 ), ( 0, 1 ), ( 0, 1 ), ( 1, 2 ), ( 2, 2 ), ( 3, 3 ), ( 3, 3 ), ( 3, 3 ) ) + ) + ) + + s = IECore.Rampff( ( ( 0, 1 ), ( 1, 2 ), ( 2, 2 ), ( 3, 3 ) ), IECore.RampInterpolation.MonotoneCubic ) + + # This ought to be an assertAlmostEqual, but it's quicker to just hardcode the value of 5/3 that + # doesn't work out exactly right + self.assertEqual( + s.evaluator(), + IECore.Splineff( IECore.CubicBasisf.bezier(), ( + ( 0, 1), ( 1/3, 1), ( 2/3, 2 ), + ( 1, 2 ), ( 4/3, 2 ), ( 1.6666667461395264, 2 ), + ( 2, 2 ), ( 7/3, 2 ), ( 8/3, 3 ), + ( 3, 3 ) + ) ) + ) + +if __name__ == "__main__": + unittest.main() + diff --git a/test/IECoreScene/SceneCacheTest.py b/test/IECoreScene/SceneCacheTest.py index df462c3f69..c1d6d863e1 100644 --- a/test/IECoreScene/SceneCacheTest.py +++ b/test/IECoreScene/SceneCacheTest.py @@ -39,6 +39,7 @@ import shutil import os import tempfile +import pathlib import IECore import IECoreScene @@ -1200,6 +1201,81 @@ def testObjectVectorShaderCompatibility( self ) : for a in nonShaderAttributes : self.assertEqual( c.readAttribute( a, 0 ), objectVector ) + def testDeprecatedSplines( self ) : + + # Test with a file saved from old Gaffer, this hopefully makes sure this is working in practice + fileName = pathlib.Path( __file__ ).parent / "data" / "oldSplines.scc" + root = IECoreScene.SceneInterface.create( str( fileName ), IECore.IndexedIO.OpenMode.Read ) + child = root.child( "cube" ) + + shaderNetwork = child.readAttribute( "ai:surface", 0 ) + self.assertEqual( + shaderNetwork.getShader( "ColorSpline" ).parameters["spline"], + IECore.RampfColor3fData( IECore.RampfColor3f( + ( + ( 0, imath.Color3f( 0, 0, 0 ) ), + ( 0.4, imath.Color3f( 0.3, 0.5, 0.9 ) ), + ( 0.7, imath.Color3f( 0.8, 0.7, 0.3) ), + ( 1, imath.Color3f( 1, 1, 1 ) ) + ), + IECore.RampInterpolation.Linear + ) ) + ) + + self.assertEqual( + shaderNetwork.getShader( "FloatSpline" ).parameters["spline"], + IECore.RampffData(IECore.Rampff( + ( + ( 0, 0 ), + ( 0.3, 0.8 ), + ( 0.7, 0.7 ), + ( 0.9, 0.1 ), + ( 1, 1 ), + ), + IECore.RampInterpolation.BSpline + ) ) + ) + + # A more synthetic test to exercise the compatibility for all spline types + + for interp in IECore.RampInterpolation.values.keys(): + ramp = IECore.RampfColor3f( + ( + ( 0, imath.Color3f( 0 ) ), + ( 0.3, imath.Color3f( 0.8, 0.5, 0.4 ) ), + ( 0.7, imath.Color3f( 0.7, 0.1, 0.2 ) ), + ( 0.9, imath.Color3f( 0.1, 0.4, 0.6 ) ), + ( 1, imath.Color3f( 1 ) ), + ), + IECore.RampInterpolation( interp ) + ) + + s = IECoreScene.Shader( "standard_surface" ) + + # Instead of writing out the Ramp, write out the Ramp's evaluator. This is wrong, but it matches + # how things used to work, when Spline was the primary representation. + s.parameters["test"] = IECore.SplinefColor3fData( ramp.evaluator() ) + shaderNetwork = IECoreScene.ShaderNetwork( + shaders = { + "shader" : s + }, + output = "shader", + ) + + io = IECore.MemoryIndexedIO( IECore.CharVectorData(), IECore.IndexedIO.OpenMode.Write ) + scc = IECoreScene.SceneCache( io ) + c = scc.createChild( "c" ) + + c.writeAttribute( "testShader", shaderNetwork, 0 ) + + del c, scc + + io = IECore.MemoryIndexedIO( io.buffer(), IECore.IndexedIO.OpenMode.Read ) + scc = IECoreScene.SceneCache( io ) + c = scc.child( "c" ) + + self.assertEqual( c.readAttribute( "testShader", 0 ).getShader( "shader" ).parameters["test"].value, ramp ) + def testCopyReturnValues( self ): fileName = os.path.join( self.tempDir, "test.scc" ) diff --git a/test/IECoreScene/ShaderNetworkAlgoTest.py b/test/IECoreScene/ShaderNetworkAlgoTest.py index ab05dc5418..554a249cd2 100644 --- a/test/IECoreScene/ShaderNetworkAlgoTest.py +++ b/test/IECoreScene/ShaderNetworkAlgoTest.py @@ -116,8 +116,6 @@ def testRemoveUnusedShaders( self ) : def testConvertColorComponentOutputConnection( self ) : - # OSL < 1.10 - n = IECoreScene.ShaderNetwork( shaders = { "texture" : IECoreScene.Shader( "noise", "osl:shader" ), @@ -133,44 +131,7 @@ def testConvertColorComponentOutputConnection( self ) : self.assertEqual( len( n ), 2 ) - IECoreScene.ShaderNetworkAlgo.convertOSLComponentConnections( n ) - self.assertEqual( len( n ), 4 ) - - self.assertEqual( len( n.inputConnections( "surface" ) ), 3 ) - self.assertEqual( len( n.inputConnections( "texture" ) ), 0 ) - - kdInput = n.input( ( "surface", "Kd" ) ) - self.assertEqual( kdInput.name, "out" ) - self.assertEqual( n.getShader( kdInput.shader ).type, "osl:shader" ) - self.assertEqual( n.getShader( kdInput.shader ).name, "MaterialX/mx_swizzle_color_float" ) - self.assertEqual( n.getShader( kdInput.shader ).parameters["channels"].value, "r" ) - - ksInput = n.input( ( "surface", "Ks" ) ) - self.assertEqual( ksInput.name, "out" ) - self.assertEqual( n.getShader( ksInput.shader ).type, "osl:shader" ) - self.assertEqual( n.getShader( ksInput.shader ).name, "MaterialX/mx_swizzle_color_float" ) - self.assertEqual( n.getShader( ksInput.shader ).parameters["channels"].value, "g" ) - - self.assertEqual( n.input( ( "surface", "Kt" ) ), ksInput ) - - # OSL > 1.10 - - n = IECoreScene.ShaderNetwork( - shaders = { - "texture" : IECoreScene.Shader( "noise", "osl:shader" ), - "surface" : IECoreScene.Shader( "plastic", "osl:surface" ), - }, - connections = [ - ( ( "texture", "out.r" ), ( "surface", "Kd" ) ), - ( ( "texture", "out.g" ), ( "surface", "Ks" ) ), - ( ( "texture", "out.g" ), ( "surface", "Kt" ) ), - ], - output = "surface" - ) - - self.assertEqual( len( n ), 2 ) - - IECoreScene.ShaderNetworkAlgo.convertOSLComponentConnections( n, 11000 ) + IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) self.assertEqual( len( n ), 2 ) self.assertEqual( len( n.inputConnections( "surface" ) ), 3 ) @@ -182,50 +143,6 @@ def testConvertColorComponentOutputConnection( self ) : def testConvertColorComponentInputConnection( self ) : - # OSL < 1.10 - - n = IECoreScene.ShaderNetwork( - shaders = { - "texture1" : IECoreScene.Shader( "floatNoise", "osl:shader" ), - "texture2" : IECoreScene.Shader( "floatNoise", "osl:shader" ), - "surface" : IECoreScene.Shader( - "plastic", "osl:surface", - parameters = { "Cs" : imath.Color3f( 0.2, 0.3, 0.4 ) } - ) - }, - connections = [ - ( ( "texture1", "out" ), ( "surface", "Cs.r" ) ), - ( ( "texture2", "out" ), ( "surface", "Cs.b" ) ), - ], - output = "surface" - ) - - self.assertEqual( len( n ), 3 ) - - IECoreScene.ShaderNetworkAlgo.convertOSLComponentConnections( n ) - self.assertEqual( len( n ), 4 ) - - self.assertFalse( n.input( ( "surface", "Cs.r" ) ) ) - self.assertFalse( n.input( ( "surface", "Cs.g" ) ) ) - self.assertFalse( n.input( ( "surface", "Cs.b" ) ) ) - - csInput = n.input( ( "surface", "Cs" ) ) - self.assertEqual( csInput.name, "out" ) - - packShader = n.getShader( csInput.shader ) - self.assertEqual( packShader.name, "MaterialX/mx_pack_color" ) - self.assertEqual( packShader.type, "osl:shader" ) - - self.assertEqual( packShader.parameters["in1"], IECore.FloatData( 0.2 ) ) - self.assertEqual( packShader.parameters["in2"], IECore.FloatData( 0.3 ) ) - self.assertEqual( packShader.parameters["in3"], IECore.FloatData( 0.4 ) ) - - self.assertEqual( n.input( ( csInput.shader, "in1" ) ), ( "texture1", "out" ) ) - self.assertEqual( n.input( ( csInput.shader, "in2" ) ), ( "", "" ) ) - self.assertEqual( n.input( ( csInput.shader, "in3" ) ), ( "texture2", "out" ) ) - - # OSL > 1.10 - n = IECoreScene.ShaderNetwork( shaders = { "texture1" : IECoreScene.Shader( "floatNoise", "osl:shader" ), @@ -244,7 +161,7 @@ def testConvertColorComponentInputConnection( self ) : self.assertEqual( len( n ), 3 ) - IECoreScene.ShaderNetworkAlgo.convertOSLComponentConnections( n, 11000 ) + IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) self.assertEqual( len( n ), 3 ) self.assertEqual( n.input( ( "surface", "Cs[0]" ) ), ( "texture1", "out" ) ) @@ -268,7 +185,7 @@ def testArnoldComponentConnectionsNotConverted( self ) : ) n2 = n.copy() - IECoreScene.ShaderNetworkAlgo.convertOSLComponentConnections( n2 ) + IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n2, 11000 ) self.assertEqual( n, n2 ) def testAddRemoveComponentConnectionAdapters( self ) : @@ -392,18 +309,28 @@ def testConvertObjectVector( self ) : self.assertEqual( IECoreScene.ShaderNetworkAlgo.convertObjectVector( objectVector ), shaderNetwork ) - def testSplineConversion( self ): + def testRampConversion( self ): parms = IECore.CompoundData() - parms["testffbSpline"] = IECore.SplineffData( IECore.Splineff( IECore.CubicBasisf.bSpline(), - ( ( 0, 1 ), ( 10, 2 ), ( 20, 0 ), ( 21, 2 ) ) ) ) - parms["testffbezier"] = IECore.SplineffData( IECore.Splineff( IECore.CubicBasisf.bSpline(), - ( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ), ( 0.4, 4 ), ( 0.5, 5 ) ) ) ) - parms["testfColor3fcatmullRom"] = IECore.SplinefColor3fData( IECore.SplinefColor3f( IECore.CubicBasisf.catmullRom(), -( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ), ( 30, imath.Color3f(5) ), ( 40, imath.Color3f(2) ), ( 50, imath.Color3f(6) ) ) ) ) - parms["testfColor3flinear"] = IECore.SplinefColor3fData( IECore.SplinefColor3f( IECore.CubicBasisf.linear(), -( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ) ) ) ) - parms["testffconstant"] = IECore.SplineffData( IECore.Splineff( IECore.CubicBasisf.constant(), - ( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ) ) ) ) + parms["testffbSpline"] = IECore.RampffData( IECore.Rampff( + ( ( 0, 1 ), ( 10, 2 ), ( 20, 0 ), ( 21, 2 ) ), + IECore.RampInterpolation.BSpline + ) ) + parms["testffmonotoneCubic"] = IECore.RampffData( IECore.Rampff( + ( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ), ( 0.4, 4 ), ( 0.5, 5 ) ), + IECore.RampInterpolation.MonotoneCubic + ) ) + parms["testfColor3fcatmullRom"] = IECore.RampfColor3fData( IECore.RampfColor3f( + ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ), ( 30, imath.Color3f(5) ), ( 40, imath.Color3f(2) ), ( 50, imath.Color3f(6) ) ), + IECore.RampInterpolation.CatmullRom + ) ) + parms["testfColor3flinear"] = IECore.RampfColor3fData( IECore.RampfColor3f( + ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ) ), + IECore.RampInterpolation.Linear + ) ) + parms["testffconstant"] = IECore.RampffData( IECore.Rampff( + ( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ) ), + IECore.RampInterpolation.Constant + ) ) shaderNetworkOrig = IECoreScene.ShaderNetwork( shaders = { @@ -413,7 +340,7 @@ def testSplineConversion( self ): output = "test" ) shaderNetwork = shaderNetworkOrig.copy() - IECoreScene.ShaderNetworkAlgo.expandSplines( shaderNetwork ) + IECoreScene.ShaderNetworkAlgo.expandRamps( shaderNetwork ) parmsExpanded = shaderNetwork.outputShader().parameters @@ -424,30 +351,31 @@ def testSplineConversion( self ): self.assertEqual( type( parmsExpanded["testfColor3fcatmullRomValues"] ), IECore.Color3fVectorData ) for name, extra in [ - ( "testffbSpline", 0 ), - ( "testffbezier", 0 ), - ( "testfColor3fcatmullRom", 0 ), + ( "testffbSpline", 4 ), + ( "testffmonotoneCubic", 8 ), + ( "testfColor3fcatmullRom", 2 ), ( "testfColor3flinear", 2 ), ( "testffconstant", 3 ) ]: - self.assertEqual( len( parms[name].value.keys() ) + extra, len( parmsExpanded[name + "Positions"] ) ) + with self.subTest( name = name ): + self.assertEqual( len( parms[name].value.points() ) + 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( set( riParmsExpanded.keys() ), set( [ i + suffix for suffix in [ "", "_Interpolation", "_Knots" ] for i in parms.keys() ] + [ 'testffbSpline_Floats', 'testffmonotoneCubic_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["testffbSpline_Knots"], IECore.FloatVectorData( [ 0, 0, 0, 10, 20, 21, 21, 21 ] ) ) + self.assertEqual( riParmsExpanded["testffbSpline_Floats"], IECore.FloatVectorData( [ 1, 1, 1, 2, 0, 2, 2, 2 ] ) ) + self.assertEqual( riParmsExpanded["testffbSpline"], IECore.IntData( 8 ) ) + self.assertEqual( riParmsExpanded["testfColor3fcatmullRom_Knots"], IECore.FloatVectorData( [ 0, 0, 10, 20, 30, 40, 50, 50 ] ) ) + self.assertEqual( riParmsExpanded["testfColor3fcatmullRom_Colors"], IECore.Color3fVectorData( [ imath.Color3f( i ) for i in [ 1, 1, 2, 0, 5, 2, 6, 6 ] ] ) ) + self.assertEqual( riParmsExpanded["testfColor3fcatmullRom"], IECore.IntData( 8 ) ) 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 ) + IECoreScene.ShaderNetworkAlgo.collapseRamps( shaderNetwork ) self.assertEqual( shaderNetwork, shaderNetworkOrig ) @@ -463,39 +391,39 @@ def testSplineConversion( self ): output = "test" ) - shaderNetworkBadSplineConnection = shaderNetworkExpandedGood.copy() - shaderNetworkBadSplineConnection.addConnection( ( ( "adapt", "out4" ), ( "test", "notASpline" ) ) ) + shaderNetworkBadRampConnection = shaderNetworkExpandedGood.copy() + shaderNetworkBadRampConnection.addConnection( ( ( "adapt", "out4" ), ( "test", "notARamp" ) ) ) with IECore.CapturingMessageHandler() as mh : - IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetworkBadSplineConnection ) + IECoreScene.ShaderNetworkAlgo.collapseRamps( shaderNetworkBadRampConnection ) self.assertEqual( len( mh.messages ), 1 ) self.assertEqual( mh.messages[0].level, IECore.Msg.Level.Error ) - self.assertEqual( mh.messages[0].message, 'Invalid spline plug name "notASpline"' ) + self.assertEqual( mh.messages[0].message, 'Invalid spline parameter name "notARamp"' ) - shaderNetworkBadSplineConnection = shaderNetworkExpandedGood.copy() - shaderNetworkBadSplineConnection.addConnection( ( ( "adapt", "out4" ), ( "test", "notASplineValues" ) ) ) + shaderNetworkBadRampConnection = shaderNetworkExpandedGood.copy() + shaderNetworkBadRampConnection.addConnection( ( ( "adapt", "out4" ), ( "test", "notARampValues" ) ) ) with IECore.CapturingMessageHandler() as mh : - IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetworkBadSplineConnection ) + IECoreScene.ShaderNetworkAlgo.collapseRamps( shaderNetworkBadRampConnection ) self.assertEqual( len( mh.messages ), 1 ) self.assertEqual( mh.messages[0].level, IECore.Msg.Level.Error ) - self.assertEqual( mh.messages[0].message, 'Invalid connection to spline parameter that doesn\'t exist "test.notASplineValues"' ) + self.assertEqual( mh.messages[0].message, 'Invalid connection to spline parameter that doesn\'t exist "test.notARampValues"' ) - shaderNetworkBadSplineConnection = shaderNetworkExpandedGood.copy() - shaderNetworkBadSplineConnection.addConnection( ( ( "adapt", "out4" ), ( "test", "testffbSplineValues" ) ) ) - shaderNetworkBadSplineConnection.addConnection( ( ( "source", "out" ), ( "adapt", "inX" ) ) ) + shaderNetworkBadRampConnection = shaderNetworkExpandedGood.copy() + shaderNetworkBadRampConnection.addConnection( ( ( "adapt", "out4" ), ( "test", "testffbSplineValues" ) ) ) + shaderNetworkBadRampConnection.addConnection( ( ( "source", "out" ), ( "adapt", "inX" ) ) ) with IECore.CapturingMessageHandler() as mh : - IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetworkBadSplineConnection ) + IECoreScene.ShaderNetworkAlgo.collapseRamps( shaderNetworkBadRampConnection ) self.assertEqual( len( mh.messages ), 1 ) self.assertEqual( mh.messages[0].level, IECore.Msg.Level.Error ) self.assertEqual( mh.messages[0].message, 'Invalid spline adapter input name "inX"' ) - # Test with partial splines that don't have the right parameters to be valid ( these silently do - # nothing, because maybe you just happen to have some parameters that look a bit like splines ) + # Test with partial ramps that don't have the right parameters to be valid ( these silently do + # nothing, because maybe you just happen to have some parameters that look a bit like ramps ) del parmsExpanded["testffbSplineBasis"] - del parmsExpanded["testffbezierValues"] + del parmsExpanded["testffmonotoneCubicValues"] del parmsExpanded["testfColor3fcatmullRomPositions"] del parmsExpanded["testfColor3flinearBasis"] del parmsExpanded["testffconstantPositions"] @@ -505,12 +433,12 @@ def testSplineConversion( self ): output = "test" ) shaderNetworkInvalid = shaderNetworkInvalidOrig.copy() - IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetworkInvalid ) + IECoreScene.ShaderNetworkAlgo.collapseRamps( shaderNetworkInvalid ) self.assertEqual( shaderNetworkInvalid, shaderNetworkInvalidOrig ) riParmsExpandedBadCounts = riParmsExpanded.copy() del riParmsExpandedBadCounts["testffbSpline"] - riParmsExpandedBadCounts["testffbezier"] = IECore.IntData( 10 ) + riParmsExpandedBadCounts["testffmonotoneCubic"] = IECore.IntData( 10 ) riShaderNetworkBadCounts = IECoreScene.ShaderNetwork( shaders = { "test" : IECoreScene.Shader( "PxrTest", "osl:shader", riParmsExpandedBadCounts ) }, @@ -518,39 +446,39 @@ def testSplineConversion( self ): ) messageHandler = IECore.CapturingMessageHandler() with messageHandler: - IECoreScene.ShaderNetworkAlgo.collapseSplines( riShaderNetworkBadCounts ) + IECoreScene.ShaderNetworkAlgo.collapseRamps( 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"' + 'Spline count "testffmonotoneCubic" does not match length of data: 10 != 13"' ] ) ) # IF we re-expand things, the counts have just been recomputed, and everything is back the way it was before - IECoreScene.ShaderNetworkAlgo.expandSplines( riShaderNetworkBadCounts ) + IECoreScene.ShaderNetworkAlgo.expandRamps( riShaderNetworkBadCounts ) self.assertEqual( riShaderNetworkBadCounts.getShader( "test" ).parameters, riParmsExpanded ) - def testSplineInputs( self ): + def testRampInputs( self ): - fC3fcatmullRom = IECore.SplinefColor3fData( IECore.SplinefColor3f( - IECore.CubicBasisf.catmullRom(), - ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ), ( 30, imath.Color3f(5) ), ( 40, imath.Color3f(2) ), ( 50, imath.Color3f(6) ) ) + fC3fcatmullRom = IECore.RampfColor3fData( IECore.RampfColor3f( + ( ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ), ( 30, imath.Color3f(5) ), ( 40, imath.Color3f(2) ) ), + IECore.RampInterpolation.CatmullRom ) ) - fC3flinear = IECore.SplinefColor3fData( IECore.SplinefColor3f( - IECore.CubicBasisf.linear(), - ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ) ) + fC3flinear = IECore.RampfColor3fData( IECore.RampfColor3f( + ( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ) ), + IECore.RampInterpolation.Linear ) ) - ffconstant = IECore.SplineffData( IECore.Splineff( - IECore.CubicBasisf.constant(), - ( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ) ) + ffconstant = IECore.RampffData( IECore.Rampff( + ( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ) ), + IECore.RampInterpolation.Constant ) ) nOrig = IECoreScene.ShaderNetwork( shaders = { - "testSplines" : IECoreScene.Shader( - "testSplines", "osl:shader", + "testRamps" : IECoreScene.Shader( + "testRamps", "osl:shader", parameters = { "fC3fcatmullRom" : fC3fcatmullRom, "fC3flinear" : fC3flinear, @@ -561,12 +489,12 @@ def testSplineInputs( self ): "colorSource" : IECoreScene.Shader( "colorSource", "osl:shader" ), }, connections = [ - ( ( "colorSource", "out" ), ( "testSplines", "fC3fcatmullRom[1].y" ) ), - ( ( "floatSource", "out" ), ( "testSplines", "fC3fcatmullRom[3].y.b" ) ), - ( ( "floatSource", "out" ), ( "testSplines", "fC3flinear[0].y.g" ) ), - ( ( "floatSource", "out" ), ( "testSplines", "ffconstant[2].y" ) ), + ( ( "colorSource", "out" ), ( "testRamps", "fC3fcatmullRom[1].y" ) ), + ( ( "floatSource", "out" ), ( "testRamps", "fC3fcatmullRom[3].y.b" ) ), + ( ( "floatSource", "out" ), ( "testRamps", "fC3flinear[0].y.g" ) ), + ( ( "floatSource", "out" ), ( "testRamps", "ffconstant[2].y" ) ), ], - output = "testSplines" + output = "testRamps" ) n = nOrig.copy() @@ -574,134 +502,149 @@ def testSplineInputs( self ): self.assertEqual( len( nOrig ), 3 ) self.assertEqual( len( n ), 6 ) - convertedSplineParameters = n.getShader( "testSplines" ).parameters - self.assertEqual( convertedSplineParameters["fC3fcatmullRomPositions"], IECore.FloatVectorData( [ 0, 10, 20, 30, 40, 50 ] ) ) - self.assertEqual( convertedSplineParameters["fC3flinearPositions"], IECore.FloatVectorData( [ 0, 0, 10, 10 ] ) ) - self.assertEqual( convertedSplineParameters["ffconstantPositions"], IECore.FloatVectorData( [ 0, 0, 0.2, 0.3, 0.3, 0.3 ] ) ) + convertedRampParameters = n.getShader( "testRamps" ).parameters + self.assertEqual( convertedRampParameters["fC3fcatmullRomPositions"], IECore.FloatVectorData( [ 10, 10, 20, 30, 40, 40 ] ) ) + self.assertEqual( convertedRampParameters["fC3flinearPositions"], IECore.FloatVectorData( [ 0, 0, 10, 10 ] ) ) + self.assertEqual( convertedRampParameters["ffconstantPositions"], IECore.FloatVectorData( [ 0, 0, 0.2, 0.3, 0.3, 0.3 ] ) ) - self.assertEqual( n.input( ( "testSplines", "fC3fcatmullRomValues" ) ), ( "testSplines_fC3fcatmullRomInputArrayAdapter", "out6" ) ) - self.assertEqual( n.input( ( "testSplines", "fC3flinearValues" ) ), ( "testSplines_fC3flinearInputArrayAdapter", "out4" ) ) - self.assertEqual( n.input( ( "testSplines", "ffconstantValues" ) ), ( "testSplines_ffconstantInputArrayAdapter", "out6" ) ) + self.assertEqual( n.input( ( "testRamps", "fC3fcatmullRomValues" ) ), ( "testRamps_fC3fcatmullRomInputArrayAdapter", "out6" ) ) + self.assertEqual( n.input( ( "testRamps", "fC3flinearValues" ) ), ( "testRamps_fC3flinearInputArrayAdapter", "out4" ) ) + self.assertEqual( n.input( ( "testRamps", "ffconstantValues" ) ), ( "testRamps_ffconstantInputArrayAdapter", "out6" ) ) - adapterParameters = n.getShader( "testSplines_fC3fcatmullRomInputArrayAdapter" ).parameters + adapterParameters = n.getShader( "testRamps_fC3fcatmullRomInputArrayAdapter" ).parameters self.assertEqual( [ adapterParameters["in%i"%i].value for i in range( 6 ) ], - [ imath.Color3f(1), imath.Color3f(2), imath.Color3f(0), imath.Color3f(5), imath.Color3f(2), imath.Color3f(6) ] + [ imath.Color3f(2), imath.Color3f(2), imath.Color3f(0), imath.Color3f(5), imath.Color3f(2), imath.Color3f(2) ] ) - self.assertEqual( len( n.inputConnections( "testSplines_fC3fcatmullRomInputArrayAdapter" ) ), 2 ) - self.assertEqual( n.input( ( "testSplines_fC3fcatmullRomInputArrayAdapter", "in1" ) ), ( "colorSource", "out" ) ) - self.assertEqual( n.input( ( "testSplines_fC3fcatmullRomInputArrayAdapter", "in3[2]" ) ), ( "floatSource", "out" ) ) - adapter1Parameters = n.getShader( "testSplines_fC3flinearInputArrayAdapter" ).parameters + # The connections have been offset to match the resulting spline, and the end point connection + # has been duplicated + self.assertEqual( len( n.inputConnections( "testRamps_fC3fcatmullRomInputArrayAdapter" ) ), 3 ) + self.assertEqual( n.input( ( "testRamps_fC3fcatmullRomInputArrayAdapter", "in2" ) ), ( "colorSource", "out" ) ) + self.assertEqual( n.input( ( "testRamps_fC3fcatmullRomInputArrayAdapter", "in4[2]" ) ), ( "floatSource", "out" ) ) + self.assertEqual( n.input( ( "testRamps_fC3fcatmullRomInputArrayAdapter", "in5[2]" ) ), ( "floatSource", "out" ) ) + + adapter1Parameters = n.getShader( "testRamps_fC3flinearInputArrayAdapter" ).parameters + + adapter1Parameters = n.getShader( "testRamps_fC3flinearInputArrayAdapter" ).parameters self.assertEqual( [ adapter1Parameters["in%i"%i].value for i in range( 4 ) ], [ imath.Color3f(1), imath.Color3f(1), imath.Color3f(2), imath.Color3f(2) ] ) - self.assertEqual( len( n.inputConnections( "testSplines_fC3flinearInputArrayAdapter" ) ), 1 ) - self.assertEqual( n.input( ( "testSplines_fC3flinearInputArrayAdapter", "in1[1]" ) ), ( "floatSource", "out" ) ) + self.assertEqual( len( n.inputConnections( "testRamps_fC3flinearInputArrayAdapter" ) ), 2 ) + self.assertEqual( n.input( ( "testRamps_fC3flinearInputArrayAdapter", "in0[1]" ) ), ( "floatSource", "out" ) ) + self.assertEqual( n.input( ( "testRamps_fC3flinearInputArrayAdapter", "in1[1]" ) ), ( "floatSource", "out" ) ) - adapter2Parameters = n.getShader( "testSplines_ffconstantInputArrayAdapter" ).parameters + adapter2Parameters = n.getShader( "testRamps_ffconstantInputArrayAdapter" ).parameters self.assertEqual( [ adapter2Parameters["in%i"%i].value for i in range( 6 ) ], [ 1, 1, 6, 7, 7, 7 ] ) - self.assertEqual( len( n.inputConnections( "testSplines_ffconstantInputArrayAdapter" ) ), 1 ) - self.assertEqual( n.input( ( "testSplines_ffconstantInputArrayAdapter", "in3" ) ), ( "floatSource", "out" ) ) + self.assertEqual( len( n.inputConnections( "testRamps_ffconstantInputArrayAdapter" ) ), 3 ) + self.assertEqual( n.input( ( "testRamps_ffconstantInputArrayAdapter", "in3" ) ), ( "floatSource", "out" ) ) + self.assertEqual( n.input( ( "testRamps_ffconstantInputArrayAdapter", "in4" ) ), ( "floatSource", "out" ) ) + self.assertEqual( n.input( ( "testRamps_ffconstantInputArrayAdapter", "in5" ) ), ( "floatSource", "out" ) ) - # Check that we can get the same results using expandSplines, aside from convertToOSLConventions + # Check that we can get the same results using expandRamps, aside from convertToOSLConventions # also changing the component syntax - nSplinesOnly = nOrig.copy() - IECoreScene.ShaderNetworkAlgo.expandSplines( nSplinesOnly ) - self.assertEqual( nSplinesOnly.input( ( "testSplines_fC3fcatmullRomInputArrayAdapter", "in3.b" ) ), ( "floatSource", "out" ) ) - self.assertEqual( nSplinesOnly.input( ( "testSplines_fC3flinearInputArrayAdapter", "in1.g" ) ), ( "floatSource", "out" ) ) - nSplinesOnly.removeConnection( ( ( "floatSource", "out" ), ( "testSplines_fC3fcatmullRomInputArrayAdapter", "in3.b" ) ) ) - nSplinesOnly.removeConnection( ( ( "floatSource", "out" ), ( "testSplines_fC3flinearInputArrayAdapter", "in1.g" ) ) ) - nSplinesOnly.addConnection( ( ( "floatSource", "out" ), ( "testSplines_fC3fcatmullRomInputArrayAdapter", "in3[2]" ) ) ) - nSplinesOnly.addConnection( ( ( "floatSource", "out" ), ( "testSplines_fC3flinearInputArrayAdapter", "in1[1]" ) ) ) - self.assertEqual( n, nSplinesOnly ) - - # But expandSplines won't do anything if the prefix is wrong + nRampsOnly = nOrig.copy() + IECoreScene.ShaderNetworkAlgo.expandRamps( nRampsOnly ) + self.assertEqual( nRampsOnly.input( ( "testRamps_fC3fcatmullRomInputArrayAdapter", "in4.b" ) ), ( "floatSource", "out" ) ) + self.assertEqual( nRampsOnly.input( ( "testRamps_fC3fcatmullRomInputArrayAdapter", "in5.b" ) ), ( "floatSource", "out" ) ) + self.assertEqual( nRampsOnly.input( ( "testRamps_fC3flinearInputArrayAdapter", "in0.g" ) ), ( "floatSource", "out" ) ) + self.assertEqual( nRampsOnly.input( ( "testRamps_fC3flinearInputArrayAdapter", "in1.g" ) ), ( "floatSource", "out" ) ) + nRampsOnly.removeConnection( ( ( "floatSource", "out" ), ( "testRamps_fC3fcatmullRomInputArrayAdapter", "in4.b" ) ) ) + nRampsOnly.removeConnection( ( ( "floatSource", "out" ), ( "testRamps_fC3fcatmullRomInputArrayAdapter", "in5.b" ) ) ) + nRampsOnly.removeConnection( ( ( "floatSource", "out" ), ( "testRamps_fC3flinearInputArrayAdapter", "in0.g" ) ) ) + nRampsOnly.removeConnection( ( ( "floatSource", "out" ), ( "testRamps_fC3flinearInputArrayAdapter", "in1.g" ) ) ) + nRampsOnly.addConnection( ( ( "floatSource", "out" ), ( "testRamps_fC3fcatmullRomInputArrayAdapter", "in4[2]" ) ) ) + nRampsOnly.addConnection( ( ( "floatSource", "out" ), ( "testRamps_fC3fcatmullRomInputArrayAdapter", "in5[2]" ) ) ) + nRampsOnly.addConnection( ( ( "floatSource", "out" ), ( "testRamps_fC3flinearInputArrayAdapter", "in0[1]" ) ) ) + nRampsOnly.addConnection( ( ( "floatSource", "out" ), ( "testRamps_fC3flinearInputArrayAdapter", "in1[1]" ) ) ) + self.assertEqual( n, nRampsOnly ) + + # But expandRamps won't do anything if the prefix is wrong nWrongPrefix = nOrig.copy() - IECoreScene.ShaderNetworkAlgo.expandSplines( nWrongPrefix, "foo:" ) + IECoreScene.ShaderNetworkAlgo.expandRamps( nWrongPrefix, "foo:" ) self.assertEqual( nOrig, nWrongPrefix ) - # Check that collapseSplines does the reverse + # Check that collapseRamps does the reverse nReverse = nOrig.copy() - IECoreScene.ShaderNetworkAlgo.expandSplines( nReverse ) - IECoreScene.ShaderNetworkAlgo.collapseSplines( nReverse ) + IECoreScene.ShaderNetworkAlgo.expandRamps( nReverse ) + IECoreScene.ShaderNetworkAlgo.collapseRamps( nReverse ) self.assertEqual( nOrig, nReverse ) n = IECoreScene.ShaderNetwork( shaders = { - "testSplines" : IECoreScene.Shader( - "testSplines", "osl:shader", + "testRamps" : IECoreScene.Shader( + "testRamps", "osl:shader", parameters = { - "fC3fcatmullRom" : IECore.SplinefColor3fData( - IECore.SplinefColor3f( IECore.CubicBasisf.catmullRom(), [ ( 0, imath.Color3f(0) ) ] * 33 ) + "fC3fcatmullRom" : IECore.RampfColor3fData( + IECore.RampfColor3f( [ ( 0, imath.Color3f(0) ) ] * 33, IECore.RampInterpolation.CatmullRom ) ), } ), "floatSource" : IECoreScene.Shader( "floatSource", "osl:shader" ), }, connections = [ - ( ( "floatSource", "out" ), ( "testSplines", "fC3fcatmullRom[0].y.b" ) ), + ( ( "floatSource", "out" ), ( "testRamps", "fC3fcatmullRom[0].y.b" ) ), ], - output = "testSplines" + output = "testRamps" ) - with self.assertRaisesRegex( Exception, r".*Cannot handle input to testSplines.fC3fcatmullRom\[0\].y.b : expanded spline has 33 control points, but max input adapter size is 32.*" ): + with self.assertRaisesRegex( Exception, r".*Cannot handle input to testRamps.fC3fcatmullRom\[0\].y.b : expanded spline has 35 control points, but max input adapter size is 32.*" ): IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) n = IECoreScene.ShaderNetwork( shaders = { - "testSplines" : IECoreScene.Shader( - "testSplines", "osl:shader", + "testRamps" : IECoreScene.Shader( + "testRamps", "osl:shader", parameters = { "fC3fcatmullRom" : fC3fcatmullRom } ), "floatSource" : IECoreScene.Shader( "floatSource", "osl:shader" ), }, connections = [ - ( ( "floatSource", "out" ), ( "testSplines", "fC3fcatmullRom[xx].y.b" ) ), + ( ( "floatSource", "out" ), ( "testRamps", "fC3fcatmullRom[xx].y.b" ) ), ], - output = "testSplines" + output = "testRamps" ) - with self.assertRaisesRegex( Exception, "Invalid spline point index xx" ): + with self.assertRaisesRegex( Exception, "Invalid ramp point index xx" ): IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) n = IECoreScene.ShaderNetwork( shaders = { - "testSplines" : IECoreScene.Shader( - "testSplines", "osl:shader", + "testRamps" : IECoreScene.Shader( + "testRamps", "osl:shader", parameters = { "fC3fcatmullRom" : fC3fcatmullRom } ), "floatSource" : IECoreScene.Shader( "floatSource", "osl:shader" ), }, connections = [ - ( ( "floatSource", "out" ), ( "testSplines", "fC3fcatmullRom[-1].y.b" ) ), + ( ( "floatSource", "out" ), ( "testRamps", "fC3fcatmullRom[-1].y.b" ) ), ], - output = "testSplines" + output = "testRamps" ) - with self.assertRaisesRegex( Exception, "Spline index -1 is out of range in spline with 6 points." ): + with self.assertRaisesRegex( Exception, "Connection to ramp index -1 is out of range in ramp with 4 points." ): IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) n = IECoreScene.ShaderNetwork( shaders = { - "testSplines" : IECoreScene.Shader( - "testSplines", "osl:shader", + "testRamps" : IECoreScene.Shader( + "testRamps", "osl:shader", parameters = { "fC3fcatmullRom" : fC3fcatmullRom } ), "floatSource" : IECoreScene.Shader( "floatSource", "osl:shader" ), }, connections = [ - ( ( "floatSource", "out" ), ( "testSplines", "fC3fcatmullRom[100].y.b" ) ), + ( ( "floatSource", "out" ), ( "testRamps", "fC3fcatmullRom[100].y.b" ) ), ], - output = "testSplines" + output = "testRamps" ) - with self.assertRaisesRegex( Exception, "Spline index 100 is out of range in spline with 6 points." ): + with self.assertRaisesRegex( Exception, "Connection to ramp index 100 is out of range in ramp with 4 points." ): IECoreScene.ShaderNetworkAlgo.convertToOSLConventions( n, 11000 ) def testColor4ComponentConnections( self ) : diff --git a/test/IECoreScene/data/oldSplines.scc b/test/IECoreScene/data/oldSplines.scc new file mode 100644 index 0000000000..bbdb0f1008 Binary files /dev/null and b/test/IECoreScene/data/oldSplines.scc differ