diff --git a/Changes.md b/Changes.md index 217c308e076..a5333325e9b 100644 --- a/Changes.md +++ b/Changes.md @@ -5,6 +5,7 @@ Features -------- - 3Delight : Added "3Delight Cloud" renderer, for rendering using the 3Delight cloud. +- Arnold : Added Arnold Operators support. Improvements ----------- diff --git a/arnoldPlugins/gaffer.mtd b/arnoldPlugins/gaffer.mtd index d25a0405380..ae9a95fdfa6 100644 --- a/arnoldPlugins/gaffer.mtd +++ b/arnoldPlugins/gaffer.mtd @@ -3377,3 +3377,69 @@ [attr custom] gaffer.layout.index INT 6 gaffer.layout.activator STRING "modeIsCustom" + +# Standard Arnold Operators +########################################################################## + +[node collection] + + primaryInput STRING "input" + +[node disable] + + primaryInput STRING "input" + +[node include_graph] + + primaryInput STRING "input" + +[node materialx] + + primaryInput STRING "input" + + gaffer.graphEditorLayout.defaultVisibility BOOL false + + [attr input] + gaffer.layout.index INT 0 + gaffer.graphEditorLayout.visible BOOL true + + [attr enable] + gaffer.layout.index INT 1 + + [attr selection] + gaffer.layout.index INT 2 + + [attr filename] + widget STRING "filename" + gaffer.layout.index INT 3 + gaffer.path.valid BOOL true + gaffer.path.leaf BOOL true + gaffer.path.bookmarks STRING "material" + gaffer.fileSystemPath.extensions STRING "mtlx" + +[node merge] + + primaryInput STRING "input" + +[node set_parameter] + + primaryInput STRING "input" + + # Hide, because we don't support array parameters. + gaffer.nodeMenu.category STRING "" + +[node set_transform] + + primaryInput STRING "input" + gaffer.graphEditorLayout.defaultVisibility BOOL false + + [attr input] + gaffer.graphEditorLayout.visible BOOL true + +[node string_replace] + + primaryInput STRING "input" + +[node switch_operator] + + primaryInput STRING "input" diff --git a/include/GafferArnold/ArnoldOperator.h b/include/GafferArnold/ArnoldOperator.h new file mode 100644 index 00000000000..662add8f135 --- /dev/null +++ b/include/GafferArnold/ArnoldOperator.h @@ -0,0 +1,88 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. 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 John Haddon 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. +// +////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "GafferArnold/Export.h" +#include "GafferArnold/TypeIds.h" + +#include "GafferScene/GlobalsProcessor.h" +#include "GafferScene/ShaderPlug.h" + +namespace GafferArnold +{ + +class GAFFERARNOLD_API ArnoldOperator : public GafferScene::GlobalsProcessor +{ + + public : + + explicit ArnoldOperator( const std::string &name=defaultName() ); + ~ArnoldOperator() override; + + GAFFER_NODE_DECLARE_TYPE( GafferArnold::ArnoldOperator, ArnoldOperatorTypeId, GafferScene::GlobalsProcessor ); + + void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; + + enum class Mode + { + Replace, + InsertFirst, + InsertLast + }; + + GafferScene::ShaderPlug *operatorPlug(); + const GafferScene::ShaderPlug *operatorPlug() const; + + Gaffer::IntPlug *modePlug(); + const Gaffer::IntPlug *modePlug() const; + + protected : + + bool acceptsInput( const Gaffer::Plug *plug, const Gaffer::Plug *inputPlug ) const override; + + void hashProcessedGlobals( const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstCompoundObjectPtr computeProcessedGlobals( const Gaffer::Context *context, IECore::ConstCompoundObjectPtr inputGlobals ) const override; + + private : + + static size_t g_firstPlugIndex; + +}; + +IE_CORE_DECLAREPTR( ArnoldOperator ) + +} // namespace GafferArnold diff --git a/include/GafferArnold/TypeIds.h b/include/GafferArnold/TypeIds.h index 0c04c233041..65b51c7decf 100644 --- a/include/GafferArnold/TypeIds.h +++ b/include/GafferArnold/TypeIds.h @@ -58,6 +58,7 @@ enum TypeId ArnoldLightFilterTypeId = 110913, ArnoldColorManagerTypeId = 110914, ArnoldImagerTypeId = 110915, + ArnoldOperatorTypeId = 110916, LastTypeId = 110924 }; diff --git a/python/GafferArnoldTest/ArnoldOperatorTest.py b/python/GafferArnoldTest/ArnoldOperatorTest.py new file mode 100644 index 00000000000..26d2e4c4d09 --- /dev/null +++ b/python/GafferArnoldTest/ArnoldOperatorTest.py @@ -0,0 +1,131 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. 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 John Haddon 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 unittest + +import imath + +import IECoreScene + +import GafferTest +import GafferSceneTest +import GafferArnold + +class ArnoldOperatorTest( GafferSceneTest.SceneTestCase ) : + + def test( self ) : + + operator = GafferArnold.ArnoldShader() + operator.loadShader( "switch_operator" ) + operator["parameters"]["index"].setValue( 0 ) + + node = GafferArnold.ArnoldOperator() + node["operator"].setInput( operator["out"] ) + + for mode in node.Mode.values.values() : + + # Since there is no upstream operator, all modes + # should have the same effect. + node["mode"].setValue( mode ) + + self.assertEqual( + node["out"].globals()["option:ai:operator"], + operator.attributes()["ai:operator"] + ) + + def testRejectsNonOperatorInputs( self ) : + + shader = GafferArnold.ArnoldShader() + shader.loadShader( "flat" ) + + node = GafferArnold.ArnoldOperator() + self.assertFalse( node["operator"].acceptsInput( shader["out"] ) ) + + def testModes( self ) : + + def order( scene ) : + + network = scene.globals()["option:ai:operator"] + + result = [] + shaderHandle = network.getOutput().shader + while shaderHandle : + result.append( network.getShader( shaderHandle ).parameters["index"].value ) + shaderHandle = network.input( ( shaderHandle, "input" ) ).shader + + result.reverse() + return result + + operator1 = GafferArnold.ArnoldShader() + operator1.loadShader( "switch_operator" ) + operator1["parameters"]["index"].setValue( 1 ) + + operator2 = GafferArnold.ArnoldShader() + operator2.loadShader( "switch_operator" ) + operator2["parameters"]["index"].setValue( 2 ) + + operator3 = GafferArnold.ArnoldShader() + operator3.loadShader( "switch_operator" ) + operator3["parameters"]["index"].setValue( 3 ) + + node1 = GafferArnold.ArnoldOperator() + node1["operator"].setInput( operator1["out"] ) + self.assertEqual( order( node1["out"] ), [ 1 ] ) + + node2 = GafferArnold.ArnoldOperator() + node2["in"].setInput( node1["out"] ) + node2["operator"].setInput( operator2["out"] ) + self.assertEqual( order( node2["out"] ), [ 2 ] ) + + node2["mode"].setValue( GafferArnold.ArnoldOperator.Mode.InsertLast ) + self.assertEqual( order( node2["out"] ), [ 1, 2 ] ) + + node2["mode"].setValue( GafferArnold.ArnoldOperator.Mode.InsertFirst ) + self.assertEqual( order( node2["out"] ), [ 2, 1 ] ) + + node3 = GafferArnold.ArnoldOperator() + node3["in"].setInput( node2["out"] ) + node3["operator"].setInput( operator3["out"] ) + self.assertEqual( order( node3["out"] ), [ 3 ] ) + + node3["mode"].setValue( GafferArnold.ArnoldOperator.Mode.InsertLast ) + self.assertEqual( order( node3["out"] ), [ 2, 1, 3 ] ) + + node3["mode"].setValue( GafferArnold.ArnoldOperator.Mode.InsertFirst ) + self.assertEqual( order( node3["out"] ), [ 3, 2, 1 ] ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferArnoldTest/__init__.py b/python/GafferArnoldTest/__init__.py index 8295c86df80..a1d8d877c64 100644 --- a/python/GafferArnoldTest/__init__.py +++ b/python/GafferArnoldTest/__init__.py @@ -54,6 +54,7 @@ from .ArnoldLightFilterTest import ArnoldLightFilterTest from .ArnoldColorManagerTest import ArnoldColorManagerTest from .ArnoldImagerTest import ArnoldImagerTest +from .ArnoldOperatorTest import ArnoldOperatorTest from .USDLightTest import USDLightTest if __name__ == "__main__": diff --git a/python/GafferArnoldUI/ArnoldOperatorUI.py b/python/GafferArnoldUI/ArnoldOperatorUI.py new file mode 100644 index 00000000000..e7609f9e71b --- /dev/null +++ b/python/GafferArnoldUI/ArnoldOperatorUI.py @@ -0,0 +1,92 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. 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 John Haddon 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 Gaffer +import GafferArnold + +Gaffer.Metadata.registerNode( + + GafferArnold.ArnoldOperator, + + "description", + """ + Assigns an operator. This is stored as an `ai:operator` option in Gaffer's + globals. + """, + + plugs = { + + "operator" : [ + + "description", + """ + The operator to be assigned. The output of an ArnoldShader node + holding an operator should be connected here. Multiple operators may be + assigned at once by chaining them together via their `input` + parameters, and then assigning the final operator via the ArnoldOperator + node. + """, + + "noduleLayout:section", "left", + "nodule:type", "GafferUI::StandardNodule", + + ], + + "mode" : [ + + "description", + """ + The mode used to combine the `operator` input with any operators that + already exist in the globals. + + - Replace : Removes all pre-existing operators, and replaces them with + the new ones. + - InsertFirst : Inserts the new operators so that they will be run before + any pre-existing operators. + - InsertLast : Inserts the new operators so that they will be run after + any pre-existing operators. + """, + + "preset:Replace", GafferArnold.ArnoldOperator.Mode.Replace, + "preset:InsertFirst", GafferArnold.ArnoldOperator.Mode.InsertFirst, + "preset:InsertLast", GafferArnold.ArnoldOperator.Mode.InsertLast, + "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", + + ], + + } + +) diff --git a/python/GafferArnoldUI/ShaderMenu.py b/python/GafferArnoldUI/ShaderMenu.py index b563da93ceb..73157c2f0ba 100644 --- a/python/GafferArnoldUI/ShaderMenu.py +++ b/python/GafferArnoldUI/ShaderMenu.py @@ -63,7 +63,7 @@ def appendShaders( menuDefinition, prefix="/Arnold" ) : uncategorisedMenuItems = [] with IECoreArnold.UniverseBlock( writable = False ) : - it = arnold.AiUniverseGetNodeEntryIterator( arnold.AI_NODE_SHADER | arnold.AI_NODE_LIGHT | arnold.AI_NODE_COLOR_MANAGER | __AI_NODE_IMAGER ) + it = arnold.AiUniverseGetNodeEntryIterator( arnold.AI_NODE_SHADER | arnold.AI_NODE_LIGHT | arnold.AI_NODE_COLOR_MANAGER | arnold.AI_NODE_OPERATOR | __AI_NODE_IMAGER ) while not arnold.AiNodeEntryIteratorFinished( it ) : @@ -92,6 +92,9 @@ def appendShaders( menuDefinition, prefix="/Arnold" ) : menuPath = "Globals/Color Manager" nodeCreator = functools.partial( __colorManagerCreator, shaderName, nodeName ) displayName = displayName.replace( "Color Manager ", "" ) + elif arnold.AiNodeEntryGetType( nodeEntry ) == arnold.AI_NODE_OPERATOR : + menuPath = "Globals/Operators" + nodeCreator = functools.partial( __shaderCreator, shaderName, GafferArnold.ArnoldShader, nodeName ) else : assert( arnold.AiNodeEntryGetType( nodeEntry ) == __AI_NODE_IMAGER ) if [ int( x ) for x in arnold.AiGetVersion()[:3] ] < [ 7, 3, 1 ] : diff --git a/python/GafferArnoldUI/__init__.py b/python/GafferArnoldUI/__init__.py index 86c9678c1ac..88152dc31cd 100644 --- a/python/GafferArnoldUI/__init__.py +++ b/python/GafferArnoldUI/__init__.py @@ -57,6 +57,7 @@ from . import ArnoldLightFilterUI from . import ArnoldColorManagerUI from . import ArnoldImagerUI +from . import ArnoldOperatorUI from . import CacheMenu from . import GPUCache diff --git a/src/GafferArnold/ArnoldOperator.cpp b/src/GafferArnold/ArnoldOperator.cpp new file mode 100644 index 00000000000..bf0b9c4e1b1 --- /dev/null +++ b/src/GafferArnold/ArnoldOperator.cpp @@ -0,0 +1,227 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. 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 John Haddon 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 "GafferArnold/ArnoldOperator.h" + +#include "GafferScene/Shader.h" +#include "GafferScene/ShaderPlug.h" + +#include "Gaffer/StringPlug.h" + +#include "IECoreScene/ShaderNetwork.h" +#include "IECoreScene/ShaderNetworkAlgo.h" + +using namespace IECore; +using namespace IECoreScene; +using namespace Gaffer; +using namespace GafferScene; +using namespace GafferArnold; + +namespace +{ + +const InternedString g_inputParameterName( "input" ); +const InternedString g_operatorAttributeName( "ai:operator" ); +const InternedString g_operatorOptionName( "option:ai:operator" ); + +ShaderNetwork::Parameter firstInput( const ShaderNetwork *network, const InternedString &shader ) +{ + ShaderNetwork::Parameter result( shader, g_inputParameterName ); + while( true ) + { + if( auto input = network->input( result ) ) + { + result.shader = input.shader; + } + else + { + return result; + } + } +} + +} // namespace + +GAFFER_NODE_DEFINE_TYPE( ArnoldOperator ); + +size_t ArnoldOperator::g_firstPlugIndex = 0; + +ArnoldOperator::ArnoldOperator( const std::string &name ) + : GlobalsProcessor( name ) +{ + storeIndexOfNextChild( g_firstPlugIndex ); + addChild( new ShaderPlug( "operator" ) ); + addChild( new IntPlug( "mode", Plug::In, (int)Mode::Replace, (int)Mode::Replace, (int)Mode::InsertLast ) ); +} + +ArnoldOperator::~ArnoldOperator() +{ +} + +GafferScene::ShaderPlug *ArnoldOperator::operatorPlug() +{ + return getChild( g_firstPlugIndex ); +} + +const GafferScene::ShaderPlug *ArnoldOperator::operatorPlug() const +{ + return getChild( g_firstPlugIndex ); +} + +Gaffer::IntPlug *ArnoldOperator::modePlug() +{ + return getChild( g_firstPlugIndex + 1 ); +} + +const Gaffer::IntPlug *ArnoldOperator::modePlug() const +{ + return getChild( g_firstPlugIndex + 1 ); +} + +bool ArnoldOperator::acceptsInput( const Gaffer::Plug *plug, const Gaffer::Plug *inputPlug ) const +{ + if( !GlobalsProcessor::acceptsInput( plug, inputPlug ) ) + { + return false; + } + + if( plug != operatorPlug() ) + { + return true; + } + + if( !inputPlug ) + { + return true; + } + + const Plug *sourcePlug = inputPlug->source(); + auto *sourceShader = runTimeCast( sourcePlug->node() ); + if( !sourceShader ) + { + return true; + } + + const Plug *sourceShaderOutPlug = sourceShader->outPlug(); + if( !sourceShaderOutPlug ) + { + return true; + } + + if( sourcePlug != sourceShaderOutPlug && !sourceShaderOutPlug->isAncestorOf( sourcePlug ) ) + { + return true; + } + + return sourceShader->typePlug()->getValue() == "ai:operator"; +} + +void ArnoldOperator::affects( const Plug *input, AffectedPlugsContainer &outputs ) const +{ + GlobalsProcessor::affects( input, outputs ); + + if( input == operatorPlug() || input == modePlug() ) + { + outputs.push_back( outPlug()->globalsPlug() ); + } +} + +void ArnoldOperator::hashProcessedGlobals( const Gaffer::Context *context, IECore::MurmurHash &h ) const +{ + h.append( operatorPlug()->attributesHash() ); + modePlug()->hash( h ); +} + +IECore::ConstCompoundObjectPtr ArnoldOperator::computeProcessedGlobals( const Gaffer::Context *context, IECore::ConstCompoundObjectPtr inputGlobals ) const +{ + ConstCompoundObjectPtr attributes = operatorPlug()->attributes(); + if( attributes->members().empty() ) + { + return inputGlobals; + } + + const IECoreScene::ShaderNetwork *aiOperator = attributes->member( g_operatorAttributeName ); + if( !aiOperator ) + { + throw IECore::Exception( "Operator not found" ); + } + + CompoundObjectPtr result = new CompoundObject; + // Since we're not going to modify any existing members (only add new ones), + // and our result becomes const on returning it, we can directly reference + // the input members in our result without copying. Be careful not to modify + // them though! + result->members() = inputGlobals->members(); + + const Mode mode = (Mode)modePlug()->getValue(); + if( mode == Mode::InsertFirst || mode == Mode::InsertLast ) + { + const ShaderNetwork *inputOperator = inputGlobals->member( g_operatorOptionName ); + if( !inputOperator || !inputOperator->size() ) + { + result->members()[g_operatorOptionName] = const_cast( aiOperator ); + } + else + { + ShaderNetworkPtr mergedOperator = inputOperator->copy(); + ShaderNetwork::Parameter insertedOut = ShaderNetworkAlgo::addShaders( mergedOperator.get(), aiOperator ); + if( mode == Mode::InsertLast ) + { + mergedOperator->addConnection( { + mergedOperator->getOutput(), + firstInput( mergedOperator.get(), insertedOut.shader ) + } ); + mergedOperator->setOutput( insertedOut ); + } + else + { + assert( mode == Mode::InsertFirst ); + mergedOperator->addConnection( { + insertedOut, + firstInput( mergedOperator.get(), mergedOperator->getOutput().shader ) + } ); + } + result->members()[g_operatorOptionName] = mergedOperator; + } + } + else + { + assert( mode == Mode::Replace ); + result->members()[g_operatorOptionName] = const_cast( aiOperator ); + } + + return result; +} diff --git a/src/GafferArnold/ArnoldShader.cpp b/src/GafferArnold/ArnoldShader.cpp index 4fd3d4ed973..d1e7aac426c 100644 --- a/src/GafferArnold/ArnoldShader.cpp +++ b/src/GafferArnold/ArnoldShader.cpp @@ -160,6 +160,9 @@ void ArnoldShader::loadShader( const std::string &shaderName, bool keepExistingV case AI_NODE_IMAGER : type = "ai:imager"; break; + case AI_NODE_OPERATOR : + type = "ai:operator"; + break; default : type = "ai:surface"; break; @@ -231,6 +234,16 @@ bool ArnoldShader::acceptsInput( const Plug *plug, const Plug *inputPlug ) const sourceShader->typePlug()->getValue() == "ai:imager" ; } + else if ( typePlug()->getValue() == "ai:operator" ) + { + // Operator connections are limited to chaining via the `input` + // parameter. Everything else is disallowed. + return + sourceShader != this && + plug == parametersPlug()->getChild( g_inputParameterName ) && + sourceShader->typePlug()->getValue() == "ai:operator" + ; + } else { /// \todo Use Arnold's `linkable` metadata. diff --git a/src/GafferArnold/ParameterHandler.cpp b/src/GafferArnold/ParameterHandler.cpp index ca2426542ac..9f4adb4de18 100644 --- a/src/GafferArnold/ParameterHandler.cpp +++ b/src/GafferArnold/ParameterHandler.cpp @@ -67,8 +67,11 @@ using namespace GafferArnold; namespace { +const InternedString g_inputParameterName( "input" ); + const AtString g_emptyArnoldString( "" ); const AtString g_nameArnoldString( "name" ); +const AtString g_inputsArnoldString( "inputs" ); const AtString g_gafferPlugTypeArnoldString( "gaffer.plugType" ); const AtString g_gafferDefaultArnoldString( "gaffer.default" ); @@ -622,7 +625,16 @@ void ParameterHandler::setupPlugs( const AtNodeEntry *nodeEntry, Gaffer::GraphCo continue; } } - validPlugs.insert( setupPlug( nodeEntry, param, plugsParent, direction ) ); + if( AiNodeEntryGetType( nodeEntry ) == AI_NODE_OPERATOR && name == g_inputsArnoldString ) + { + // Deliberately make a singular input as we daisy-chain these and add them all + // to the global target operator's `inputs` plug using `AiOpLink` in `ShaderNetworkAlgo` + validPlugs.insert( ::setupPlug( g_inputParameterName, plugsParent, direction ) ); + } + else + { + validPlugs.insert( setupPlug( nodeEntry, param, plugsParent, direction ) ); + } } AiParamIteratorDestroy( it ); diff --git a/src/GafferArnoldModule/GafferArnoldModule.cpp b/src/GafferArnoldModule/GafferArnoldModule.cpp index 41569c67117..285f373434f 100644 --- a/src/GafferArnoldModule/GafferArnoldModule.cpp +++ b/src/GafferArnoldModule/GafferArnoldModule.cpp @@ -46,6 +46,7 @@ #include "GafferArnold/ArnoldImager.h" #include "GafferArnold/ArnoldLight.h" #include "GafferArnold/ArnoldMeshLight.h" +#include "GafferArnold/ArnoldOperator.h" #include "GafferArnold/ArnoldOptions.h" #include "GafferArnold/ArnoldRender.h" #include "GafferArnold/ArnoldShader.h" @@ -136,4 +137,13 @@ BOOST_PYTHON_MODULE( _GafferArnold ) .value( "InsertLast", ArnoldImager::Mode::InsertLast ) ; } + + { + scope s = GafferBindings::DependencyNodeClass(); + enum_( "Mode" ) + .value( "Replace", ArnoldOperator::Mode::Replace ) + .value( "InsertFirst", ArnoldOperator::Mode::InsertFirst ) + .value( "InsertLast", ArnoldOperator::Mode::InsertLast ) + ; + } } diff --git a/src/IECoreArnold/Renderer.cpp b/src/IECoreArnold/Renderer.cpp index 1cd939d19c6..4b9dc5d7b9e 100644 --- a/src/IECoreArnold/Renderer.cpp +++ b/src/IECoreArnold/Renderer.cpp @@ -69,6 +69,7 @@ #include "ai_array.h" #include "ai_msg.h" +#include "ai_operator.h" #include "ai_procedural.h" #include "ai_ray.h" #include "ai_render.h" @@ -3208,6 +3209,7 @@ const IECore::InternedString g_idAOVShaderOptionName( "ai:aov_shader:__cortexID" const IECore::InternedString g_imagerOptionName( "ai:imager" ); const IECore::InternedString g_logFileNameOptionName( "ai:log:filename" ); const IECore::InternedString g_logMaxWarningsOptionName( "ai:log:max_warnings" ); +const IECore::InternedString g_operatorOptionName( "ai:operator" ); const IECore::InternedString g_pluginSearchPathOptionName( "ai:plugin_searchpath" ); const IECore::InternedString g_profileFileNameOptionName( "ai:profileFileName" ); const IECore::InternedString g_progressiveMinAASamplesOptionName( "ai:progressive_min_AA_samples" ); @@ -3323,6 +3325,7 @@ class ArnoldGlobals m_atmosphere.reset(); m_background.reset(); m_imager.reset(); + m_operator.reset(); m_defaultCamera.reset(); // Destroy the universe while our message callback is // still active, so we catch any Arnold shutdown messages. @@ -3607,6 +3610,19 @@ class ArnoldGlobals } return; } + else if( name == g_operatorOptionName ) + { + m_operator = nullptr; + if( value ) + { + if( const IECoreScene::ShaderNetwork *d = reportedCast( value, "option", name ) ) + { + m_operator = m_shaderCache->get( d, nullptr ); + } + } + AiOpSetTarget( universe(), m_operator ? m_operator->root() : nullptr ); + return; + } else if( boost::starts_with( name.c_str(), "ai:aov_shader:" ) ) { m_aovShaders.erase( name ); @@ -4216,6 +4232,7 @@ class ArnoldGlobals ArnoldShaderPtr m_atmosphere; ArnoldShaderPtr m_background; ArnoldShaderPtr m_imager; + ArnoldShaderPtr m_operator; std::string m_cameraName; using CameraMap = tbb::concurrent_unordered_map; diff --git a/src/IECoreArnold/ShaderNetworkAlgo.cpp b/src/IECoreArnold/ShaderNetworkAlgo.cpp index 97768a497f3..1e0513aa337 100644 --- a/src/IECoreArnold/ShaderNetworkAlgo.cpp +++ b/src/IECoreArnold/ShaderNetworkAlgo.cpp @@ -53,6 +53,8 @@ #include "boost/lexical_cast.hpp" #include "boost/unordered_map.hpp" +#include "ai_operator.h" + #include using namespace std; @@ -64,6 +66,9 @@ using namespace IECoreArnold; namespace { +const InternedString g_operatorAttributeName( "ai:operator" ); +const InternedString g_inputAttributeName( "input" ); + const AtString g_codeArnoldString( "code" ); const AtString g_emptyArnoldString( "" ); const AtString g_outputArnoldString( "output" ); @@ -165,16 +170,31 @@ AtNode *convertWalk( const ShaderNetwork::Parameter &outputParameter, const IECo ParameterAlgo::setParameter( node, arnoldParameterName, namedParameter.second.get() ); } + const bool isOperator = shader->getType() == g_operatorAttributeName.string() ? true : false; + // Recurse through input connections for( const auto &connection : shaderNetwork->inputConnections( outputParameter.shader ) ) { + if( isOperator && connection.destination.name != g_inputAttributeName ) + { + // For operators, we can only connect other operator inputs + continue; + } + AtNode *sourceNode = convertWalk( connection.source, shaderNetwork, name, nodeCreator, nodes, converted, nodeParameters ); if( !sourceNode ) { continue; } + if( isOperator ) + { + // All inputs need to be added to the target operator, this is appended in `AiOpLink` later + nodes.push_back( node ); + return node; + } + string parameterName; if( isOSLShader ) { @@ -454,6 +474,13 @@ std::vector convert( const IECoreScene::ShaderNetwork *shaderNetwork, return AiNode( universe, nodeType, nodeName, parentNode ); }; convertWalk( network->getOutput(), network.get(), name, nodeCreator, result, converted, nodeParameters ); + if( network->outputShader()->getType() == g_operatorAttributeName.string() ) + { + for( std::vector::reverse_iterator riter = result.rbegin() + 1; riter != result.rend(); ++riter ) + { + AiOpLink( *riter, result.back() ); + } + } for( const auto &kv : network->outputShader()->blindData()->readable() ) { ParameterAlgo::setParameter( result.back(), AtString( kv.first.c_str() ), kv.second.get() ); diff --git a/startup/gui/menus.py b/startup/gui/menus.py index fec7f327605..a50f637dae8 100644 --- a/startup/gui/menus.py +++ b/startup/gui/menus.py @@ -128,6 +128,7 @@ def addHelpMenuItems( items ) : nodeMenu.append( "/Arnold/Globals/Background", GafferArnold.ArnoldBackground, searchText = "ArnoldBackground" ) nodeMenu.append( "/Arnold/Globals/AOVShader", GafferArnold.ArnoldAOVShader, searchText = "ArnoldAOVShader" ) nodeMenu.append( "/Arnold/Globals/Imager", GafferArnold.ArnoldImager, searchText = "ArnoldImager" ) + nodeMenu.append( "/Arnold/Globals/Operator", GafferArnold.ArnoldOperator, searchText = "ArnoldOperator" ) nodeMenu.append( "/Arnold/Displacement", GafferArnold.ArnoldDisplacement, searchText = "ArnoldDisplacement" ) nodeMenu.append( "/Arnold/CameraShaders", GafferArnold.ArnoldCameraShaders, searchText = "ArnoldCameraShaders" ) nodeMenu.append( "/Arnold/VDB", GafferArnold.ArnoldVDB, searchText = "ArnoldVDB" )