Skip to content

Commit

Permalink
Merge branch '1.3_maintenance' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
johnhaddon committed Nov 15, 2023
2 parents e41f52c + 0949bea commit e96a9d5
Show file tree
Hide file tree
Showing 17 changed files with 476 additions and 58 deletions.
17 changes: 16 additions & 1 deletion Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,35 @@ Breaking Changes
- OpenColorIOContext : Removed `configEnabledPlug()`, `configValuePlug()`, `workingSpaceEnabledPlug()` and `workingSpaceValuePlug()` methods. Use the OptionalValuePlug child accessors instead.
- Windows launch script : Removed the hardcoded `/debugexe` switch used when `GAFFER_DEBUG` is enabled, making it possible to use debuggers other than Visual Studio. Debug switches can be added to the `GAFFER_DEBUGGER` environment variable instead.

1.3.x.x (relative to 1.3.6.1)
1.3.x.x (relative to 1.3.7.0)
=======



1.3.7.0 (relative to 1.3.6.1)
=======

Improvements
------------

- ArnoldShader : Improved support for camera projections by exposing the `camera` plug on the `camera_projection` shader.
- Instancer :
- Improved scene generation for encapsulated instancers significantly, with some production scenes now generating 5-7x faster.
- Added `omitDuplicateIds` plug, to determine whether points with duplicate IDs are ignored or should trigger an error.
- ScenePathPlugValueWidget : Added fallback to browsing the focussed scene when no other scene can be found. This makes the widget suitable for use on ShaderNodes.
- Windows : Disabled Arnold's ADP usage and crash reporting module by default. Users can enable it by setting `ARNOLD_ADP_DISABLE=0` for Arnold versions after 7.1.4.0 or `ARNOLD_ADP_OPTIN=1` for earlier versions.

Fixes
-----

- Catalogue : Fixed performance regressive when saving interactive renders with multiple AOVs (introduced in 1.3.6.1).

API
---

- Capsule : Added protected `renderOptions()` and `throwIfNoScene()` methods.
- ScenePath : Added support for passing `nullptr` for the scene.
- ArnoldShaderUI : Added support for `camera` widget type metadata, to add a camera browser to a string parameter.

1.3.6.1 (relative to 1.3.6.0)
=======
Expand Down
3 changes: 2 additions & 1 deletion arnoldPlugins/gaffer.mtd
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,8 @@
gaffer.layout.activator.coordSpaceIsPref STRING "parameters['coord_space'].getValue() == 'Pref'"

[attr camera]
gaffer.plugType STRING ""
gaffer.plugType STRING "StringPlug"
widget STRING "camera"

[attr pref_name]
gaffer.layout.activator STRING "coordSpaceIsPref"
Expand Down
25 changes: 23 additions & 2 deletions bin/gaffer.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,13 @@ if "%ARNOLD_ROOT%" NEQ "" (

if exist "%ARNOLD_ROOT%\include\ai_version.h" (
for /f "tokens=3" %%A in ('findstr /R /C:"#define *AI_VERSION_ARCH_NUM" "%ARNOLD_ROOT%\include\ai_version.h"') do (
set ARNOLD_ARCH_NUM=%%A
set /a ARNOLD_ARCH_NUM=%%A
)
for /f "tokens=3" %%A in ('findstr /R /C:"#define *AI_VERSION_MAJOR_NUM" "%ARNOLD_ROOT%\include\ai_version.h"') do (
set ARNOLD_VERSION_NUM=%%A
set /a ARNOLD_VERSION_NUM=%%A
)
for /f "tokens=3" %%A in ('findstr /R /C:"#define *AI_VERSION_MINOR_NUM" "%ARNOLD_ROOT%\include\ai_version.h"') do (
set /a ARNOLD_MINOR_NUM=%%A
)

set ARNOLD_VERSION=!ARNOLD_ARCH_NUM!.!ARNOLD_VERSION_NUM!
Expand All @@ -77,6 +80,24 @@ if "%ARNOLD_ROOT%" NEQ "" (
) else (
echo WARNING : Unable to determine Arnold version
)

REM Disable Autodesk Analytics, unless it is being explicitly managed already
REM by setting `ARNOLD_ADP_OPTIN` OR `ARNOLD_ADP_DISABLE`
if "%ARNOLD_ADP_OPTIN%" EQU "" (
if "%ARNOLD_ADP_DISABLE%" EQU "" (
set /a ARNOLD_VERSION_EXPANDED=!ARNOLD_ARCH_NUM!*10000+!ARNOLD_VERSION_NUM!*100+!ARNOLD_MINOR_NUM!
if !ARNOLD_VERSION_EXPANDED! GEQ 70104 (
REM `ARNOLD_ADP_DISABLE` is new in Arnold `7.1.4.0`, and is claimed
REM to completely disable ADP.
set ARNOLD_ADP_DISABLE=1
) else (
REM In older Arnold versions, we only have `ARNOLD_ADP_OPTIN`, which
REM still allows ADP to run, but is meant to opt out of reporting.
REM In Arnold 7.1.4.0 is has been observed to cause hangs.
set ARNOLD_ADP_OPTIN=0
)
)
)
)

rem 3Delight
Expand Down
33 changes: 29 additions & 4 deletions include/IECoreArnold/ShaderNetworkAlgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,40 @@ namespace IECoreArnold
namespace ShaderNetworkAlgo
{

/// Returns a network of `AtNodes` generated by converting `shaderNetwork`
/// to Arnold. The output shader is the last node in the returned vector,
/// and is given the specified `name`. All other nodes will be named
/// uniquely using `name` as a prefix.
/// Some Arnold shaders (`camera_projection` particularly) have unhelpful `NODE *`
/// parameters that can't be exposed to users as-is. For these we allow a string
/// parameter value to specify the _name_ of a node, and connect to the node
/// itself at a later date (since the node might not exist when the shader is
/// converted, or might be deleted later).
struct NodeParameter
{
NodeParameter( AtNode *node, AtString parameterName, AtString parameterValue );
NodeParameter( const NodeParameter &other ) = default;

/// Uses `AiNodeLookUpByName()` to find the right node, and assign it to the
/// parameter.
void updateParameter() const;

private :
AtNode *m_node;
AtString m_parameterName;
AtString m_parameterValue;
};

/// Returns a network of `AtNodes` generated by converting `shaderNetwork` to
/// Arnold. The output shader is the last node in the returned vector, and is
/// given the specified `name`. All other nodes will be named uniquely using
/// `name` as a prefix. If provided, `nodeParameters` is filled for later
/// resolution.
IECOREARNOLD_API std::vector<AtNode *> convert( const IECoreScene::ShaderNetwork *shaderNetwork, AtUniverse *universe, const std::string &name, std::vector<NodeParameter> &nodeParameters, const AtNode *parentNode = nullptr );
/// \deprecated
IECOREARNOLD_API std::vector<AtNode *> convert( const IECoreScene::ShaderNetwork *shaderNetwork, AtUniverse *universe, const std::string &name, const AtNode *parentNode = nullptr );
/// Updates a previously converted set of nodes to reflect changes in `shaderNetwork`,
/// reusing AtNodes where possible. The `nodes` vector is updated in place, newly created
/// nodes use the same parent as the original nodes, and unused nodes are destroyed with
/// `AiNodeDestroy`. Returns true if the output shader node is reused.
IECOREARNOLD_API bool update( std::vector<AtNode *> &nodes, std::vector<NodeParameter> &nodeParameters, const IECoreScene::ShaderNetwork *shaderNetwork );
/// \deprecated
IECOREARNOLD_API bool update( std::vector<AtNode *> &nodes, const IECoreScene::ShaderNetwork *shaderNetwork );

/// Converts any UsdPreviewSurface shaders and UsdLuxLights into native Arnold shaders. This conversion
Expand Down
10 changes: 10 additions & 0 deletions python/GafferArnoldTest/ArnoldShaderTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -937,5 +937,15 @@ def testLoadQuadLight( self ) :
self.assertTrue( shader["parameters"]["width"].isSetToDefault() )
self.assertTrue( shader["parameters"]["height"].isSetToDefault() )

def testLoadCameraProjection( self ) :

shader = GafferArnold.ArnoldShader()
shader.loadShader( "camera_projection" )

self.assertIn( "camera", shader["parameters"] )
self.assertIsInstance( shader["parameters"]["camera"], Gaffer.StringPlug )
self.assertEqual( shader["parameters"]["camera"].getValue(), "" )
self.assertEqual( shader["parameters"]["camera"].defaultValue(), "" )

if __name__ == "__main__":
unittest.main()
8 changes: 7 additions & 1 deletion python/GafferArnoldUI/ArnoldShaderUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ def __translateNodeMetadata( nodeEntry ) :
nodeEntry, paramName, "linkable",
defaultValue = paramType not in (
arnold.AI_TYPE_BYTE, arnold.AI_TYPE_INT, arnold.AI_TYPE_UINT,
arnold.AI_TYPE_BOOLEAN, arnold.AI_TYPE_ENUM, arnold.AI_TYPE_STRING
arnold.AI_TYPE_BOOLEAN, arnold.AI_TYPE_ENUM, arnold.AI_TYPE_STRING,
arnold.AI_TYPE_NODE
)
)
__metadata[paramPath]["nodule:type"] = None if linkable else ""
Expand All @@ -287,9 +288,14 @@ def __translateNodeMetadata( nodeEntry ) :
"popup" : "GafferUI.PresetsPlugValueWidget",
"mapper" : "GafferUI.PresetsPlugValueWidget",
"filename" : "GafferUI.PathPlugValueWidget",
"camera" : "GafferSceneUI.ScenePathPlugValueWidget",
"null" : "",
}[widget]

if widget == "camera" :
__metadata[paramPath]["scenePathPlugValueWidget:setNames"] = IECore.StringVectorData( [ "__cameras" ] )
__metadata[paramPath]["scenePathPlugValueWidget:setsLabel"] = "Show only cameras"

# Layout section from OSL "page".

page = __aiMetadataGetStr( nodeEntry, paramName, "page" )
Expand Down
49 changes: 49 additions & 0 deletions python/GafferImageTest/CatalogueTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,55 @@ def testOutputIndex( self ) :
c["catalogue:imageName"] = "output:3"
self.assertImagesEqual( notFoundText["out"], catalogue["out"] )

def testYouOnlySaveOnce( self ) :

# Send a bunch of AOVs to a Catalogue image, but don't
# close the drivers yet.

script = Gaffer.ScriptNode()

script["catalogue"] = GafferImage.Catalogue()
script["catalogue"]["directory"].setValue( self.temporaryDirectory() / "catalogue" )

script["constant"] = GafferImage.Constant()
script["constant"]["format"].setValue( GafferImage.Format( 100, 100 ) )
script["constant"]["color"].setValue( imath.Color4f( 1, 0, 0, 1 ) )

drivers = []
for layer in [ "" ] + [ f"aov{n}" for n in range( 0, 10 ) ] :

script["constant"]["layer"].setValue( layer )
drivers.append( self.sendImage( script["constant"]["out"], script["catalogue"], waitForSave = False, close = False ) )

self.assertEqual( len( script["catalogue"]["images"] ), 1 )

# Now close the drivers from a background thread, as they will be by a
# real renderer, and check that the image is saved to file appropriately.

with GafferTest.ParallelAlgoTest.UIThreadCallHandler() as handler :
closingThread = threading.Thread( target = lambda : [ d.close( withCallHandler = False ) for d in drivers ] )
closingThread.start()
# In practice, renderers close drivers fast enough that all drivers
# are closed before the first `Display::imageReceivedSignal()` is
# emitted on the UI thread. Emulate this by waiting for the thread to
# finish before calling `assertCalled()`
closingThread.join()
# We now have a bunch of pending UI thread calls, one for each driver,
# so handle those. We expect the first call to start the saving process,
# but that process happens in the background and won't be visible yet.
for driver in drivers :
handler.assertCalled()
self.assertEqual( script["catalogue"]["images"][0]["fileName"].getValue(), "" )
# The saving process will make one more UI thread call, to make the
# result of the save visible. Handle that and check we now have an image
# file.
handler.assertCalled()
self.assertNotEqual( script["catalogue"]["images"][0]["fileName"].getValue(), "" )
self.assertTrue( pathlib.Path( script["catalogue"]["images"][0]["fileName"].getValue() ).is_file() )
# That should be it. There should definitely not be a whole load more
# attempts to save the image, so assert that no more UI thread calls are
# made.
handler.assertDone()

if __name__ == "__main__":
unittest.main()
6 changes: 5 additions & 1 deletion python/GafferImageTest/DisplayTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ def sendBucket( self, bucketWindow, channelData ) :
h.assertCalled()
h.assertDone()

def close( self ) :
def close( self, withCallHandler = True ) :

if not withCallHandler :
self.__driver.imageClose()
return

with GafferTest.ParallelAlgoTest.UIThreadCallHandler() as h :
self.__driver.imageClose()
Expand Down
19 changes: 15 additions & 4 deletions python/GafferSceneTest/ScenePathTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,16 +213,27 @@ def testStandardFilter( self ) :
path.setFilter( GafferScene.ScenePath.createStandardFilter( [ "__cameras" ] ) )
self.assertEqual( { str( c ) for c in path.children() }, { "/camera" } )

def testNone( self ) :
def testNoneContext( self ) :

plane = GafferScene.Plane()

with self.assertRaisesRegex( Exception, "Python argument types" ) :
GafferScene.ScenePath( None, Gaffer.Context() )

with self.assertRaisesRegex( Exception, "Python argument types" ) :
GafferScene.ScenePath( plane["out"], None )

def testNoneScene( self ) :

path = GafferScene.ScenePath( None, Gaffer.Context() )
self.assertIsNone( path.getScene() )
self.assertIsNone( path.cancellationSubject() )
self.assertFalse( path.isValid() )
self.assertEqual( path.children(), [] )

path2 = path.copy()
self.assertIsNone( path2.getScene() )
self.assertIsNone( path2.cancellationSubject() )
self.assertFalse( path2.isValid() )
self.assertEqual( path2.children(), [] )

def testGILManagement( self ) :

script = Gaffer.ScriptNode()
Expand Down
48 changes: 28 additions & 20 deletions python/GafferSceneUI/ScenePathPlugValueWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ def __init__( self, plug, path = None, **kw ) :

GafferUI.PathPlugValueWidget.__init__( self, plug, path, **kw )

plug.ancestor( Gaffer.ScriptNode ).focusChangedSignal().connect( Gaffer.WeakMethod( self.__focusChanged ), scoped = False )

def _pathChooserDialogue( self ) :

dialogue = GafferUI.PathPlugValueWidget._pathChooserDialogue( self )
Expand All @@ -85,28 +87,34 @@ def _pathChooserDialogue( self ) :

def __scenePlug( self, plug ) :

scenePlugName = Gaffer.Metadata.value( plug, "scenePathPlugValueWidget:scene" ) or "in"
scenePlug = plug.node().descendant( scenePlugName )
if scenePlug and isinstance( scenePlug, GafferScene.ScenePlug ) :
return scenePlug
# Search for a suitable ScenePlug input on the same node as this plug,
# or on the node of another plug being driven by this plug.

def predicate( plug ) :

# Couldn't find scene plug. Perhaps `plug` has been promoted but the
# corresponding scene hasn't been yet. Check outputs to see if that
# is the case.
scenePlugName = Gaffer.Metadata.value( plug, "scenePathPlugValueWidget:scene" ) or "in"
scenePlug = plug.node().descendant( scenePlugName )
if scenePlug and isinstance( scenePlug, GafferScene.ScenePlug ) :
return scenePlug

scenePlug = Gaffer.PlugAlgo.findDestination( plug, predicate )
if scenePlug is not None :
return scenePlug

for output in plug.outputs() :
p = self.__scenePlug( output )
if p is not None :
return p
# The above doesn't work well for ShaderNodes, since they don't have
# ScenePlug inputs. We _could_ traverse outputs from the shader looking
# for a ShaderAssignment node to get a ScenePlug from. But this wouldn't
# be useful if the scene hierarchy was manipulated downstream of the
# ShaderAssignment as shaders need the final paths as seen by the
# renderer. So instead use the focus node, as it is more likely to
# be pointed at the final render node.

# Or perhaps `plug` is in a cell in a spreadsheet, in which case
# we may be able to get somewhere by looking where the corresponding
# output is connected.
## \todo Can this sort of traversal be wrapped up in PlugAlgo somehow?
focusNode = plug.ancestor( Gaffer.ScriptNode ).getFocus()
if focusNode is not None :
return next( GafferScene.ScenePlug.RecursiveOutputRange( focusNode ), None )

cellPlug = plug.ancestor( Gaffer.Spreadsheet.CellPlug )
if cellPlug is not None :
spreadsheet = cellPlug.ancestor( Gaffer.Spreadsheet )
if spreadsheet is not None :
return self.__scenePlug( spreadsheet["out"][cellPlug.getName()] )
def __focusChanged( self, scriptNode, node ) :

scenePlug = self.__scenePlug( self.getPlug() )
if scenePlug is not None :
self.path().setScene( scenePlug )
Loading

0 comments on commit e96a9d5

Please sign in to comment.