diff --git a/.github/workflows/main/setBuildVars.py b/.github/workflows/main/setBuildVars.py index 7d61d5a5b3b..6f72be2b78a 100755 --- a/.github/workflows/main/setBuildVars.py +++ b/.github/workflows/main/setBuildVars.py @@ -113,6 +113,23 @@ releaseId = r.id break +if releaseId : + + # Check that the version specified by the SConstruct matches the + # version in the tag. + versions = {} + versionRe = re.compile( r"^gaffer(.*)Version = (\d+)") + with open( "SConstruct" ) as sconstruct : + for line in sconstruct.readlines() : + versionMatch = versionRe.match( line ) + if versionMatch : + versions[versionMatch.group( 1 )] = versionMatch.group( 2 ) + + version = "{Milestone}.{Major}.{Minor}.{Patch}".format( **versions ) + if version != tag : + sys.stderr.write( "Tag \"{}\" does not match SConstruct version \"{}\"\n".format( tag, version ) ) + sys.exit( 1 ) + ## Build Name # We have a couple of naming conventions for builds, depending on the nature of the trigger. diff --git a/Changes.md b/Changes.md index a95281dc367..99d93696d9a 100644 --- a/Changes.md +++ b/Changes.md @@ -1,3 +1,32 @@ +0.61.1.1 (relative to 0.61.1.0) +======== + +Fixes +----- + +- InteractiveArnoldRender : Fixed Arnold 7 crash when stopping a render with global AOV shaders. + +0.61.1.0 (relative to 0.61.0.0) +======== + +Improvements +------------ + +- OSLCode : Improved error reporting for cases where empty `.oso` files are produced. + +Fixes +----- + +- HierarchyView : Fixed unwanted expansion when an item is selected. +- Viewer : Fixed scroll wheel handling for overlay widgets. In particular this means that the mouse wheel can now be used to scroll through the parameters of the scene inspector. +- GraphEditor : Fixed highlighting of input connections for promoted ArrayPlugs. +- BranchCreator : Fixed bug in bounding box computation which could cause excessive evaluation of the input scene. + +API +--- + +- PathListingWidget : Added `columnAt()` method. + 0.61.0.0 ======== @@ -131,6 +160,14 @@ Build - PySide : Updated to version 5.15.2. - Cortex : Updated to version 10.3.0.0. +0.60.12.1 (relative to 0.60.12.0) +========= + +Fixes +----- + +- BranchCreator : Fixed bug in bounding box computation which could cause excessive evaluation of the input scene. + 0.60.12.0 (relative to 0.60.11.0) ========= diff --git a/SConstruct b/SConstruct index 2b3ecda465e..9f0625601dd 100644 --- a/SConstruct +++ b/SConstruct @@ -39,12 +39,9 @@ import os import re import sys import glob -import shutil -import fnmatch -import functools import platform -import py_compile import subprocess +import distutils.dir_util ############################################################################################### # Version @@ -52,8 +49,8 @@ import subprocess gafferMilestoneVersion = 0 # for announcing major milestones - may contain all of the below gafferMajorVersion = 61 # backwards-incompatible changes -gafferMinorVersion = 0 # new backwards-compatible features -gafferPatchVersion = 0 # bug fixes +gafferMinorVersion = 1 # new backwards-compatible features +gafferPatchVersion = 1 # bug fixes # All of the following must be considered when determining # whether or not a change is backwards-compatible @@ -1675,7 +1672,7 @@ for f in exampleFiles : def installer( target, source, env ) : - shutil.copytree( str( source[0] ), str( target[0] ), symlinks=True ) + distutils.dir_util.copy_tree( str( source[0] ), str( target[0] ), preserve_symlinks=True, update=True ) if env.subst( "$PACKAGE_FILE" ).endswith( ".dmg" ) : diff --git a/config/ie/installAll b/config/ie/installAll index b3c6fe429e8..3b8bfeeebd4 100644 --- a/config/ie/installAll +++ b/config/ie/installAll @@ -53,6 +53,9 @@ def build( extraArgs = [] ) : buildArgs.extend( sys.argv[1:] ) print( " ".join( buildArgs ) ) + if "DRYRUN=1" in sys.argv : + return + if subprocess.call( buildArgs ) != 0 : raise RuntimeError( "Error : " + " ".join( buildArgs ) ) print( "Build succeeded: " + " ".join( buildArgs ) + "\n" ) diff --git a/config/ie/postInstall b/config/ie/postInstall index 8a8d42c155f..6e7c4943ca4 100644 --- a/config/ie/postInstall +++ b/config/ie/postInstall @@ -42,11 +42,10 @@ def __link( source, target ) : if source == target : return - if os.path.isdir( target ) : - if os.path.islink( target ) : - os.unlink( target ) - else : - shutil.rmtree( target ) + if os.path.islink( target ) : + os.unlink( target ) + elif os.path.isdir( target ) : + shutil.rmtree( target ) os.symlink( os.path.relpath( source, os.path.dirname( target ) ), diff --git a/doc/source/Interface/ControlsAndShortcuts/index.md b/doc/source/Interface/ControlsAndShortcuts/index.md index e9c539f8d86..defd3d87e3a 100644 --- a/doc/source/Interface/ControlsAndShortcuts/index.md +++ b/doc/source/Interface/ControlsAndShortcuts/index.md @@ -365,6 +365,7 @@ Isolate red channel :kbd:`R` Isolate green channel :kbd:`G` Isolate blue channel :kbd:`B` Isolate alpha channel :kbd:`A` +View luminance of RGB :kbd:`L` Previous layer :kbd:`PgDn` Next layer :kbd:`PgUp` Center image at 1:1 scale :kbd:`Home` diff --git a/python/GafferSceneTest/ParentTest.py b/python/GafferSceneTest/ParentTest.py index 8dc058f8e4f..3af392af360 100644 --- a/python/GafferSceneTest/ParentTest.py +++ b/python/GafferSceneTest/ParentTest.py @@ -876,5 +876,45 @@ def testMultipleNewDestinationsBelowOneParent( self ) : IECore.InternedStringVectorData( [ "sphere", "cube", "childrenOfRoundThings", "childrenOfSquareThings" ] ) ) + def testNoUnwantedBoundEvaluations( self ) : + + reader = GafferScene.SceneReader() + reader["fileName"].setValue( "${GAFFER_ROOT}/resources/gafferBot/caches/gafferBot.scc" ) + + group = GafferScene.Group() + + parent = GafferScene.Parent() + parent["in"].setInput( reader["out"] ) + parent["children"][0].setInput( group["out"] ) + parent["parent"].setValue( "/" ) + parent["destination"].setValue( "/children" ) + + # Computing the root bound should not require more than the bounds + # of `/` and `/GAFFERBOT` to be queried from the input scene. + + with Gaffer.ContextMonitor( reader["out"]["bound"] ) as contextMonitor : + parent["out"].bound( "/" ) + + self.assertEqual( contextMonitor.combinedStatistics().numUniqueContexts(), 2 ) + + # If we parent to `/GAFFERBOT/children` then computing the bound of `/GAFFERBOT` + # should only query `/GAFFERBOT` and `/GAFFERBOT/C_torso_GRP` from the input. + + Gaffer.ValuePlug.clearCache() + Gaffer.ValuePlug.clearHashCache() + + parent["destination"].setValue( "/GAFFERBOT/children" ) + with Gaffer.ContextMonitor( reader["out"]["bound"] ) as contextMonitor : + parent["out"].bound( "/GAFFERBOT" ) + + self.assertEqual( contextMonitor.combinedStatistics().numUniqueContexts(), 2 ) + + # The bounds for children of `/GAFFERBOT` should be perfect pass throughs. + + self.assertEqual( + parent["out"].boundHash( "/GAFFERBOT/C_torso_GRP" ), + parent["in"].boundHash( "/GAFFERBOT/C_torso_GRP" ) + ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferUI/GadgetWidget.py b/python/GafferUI/GadgetWidget.py index f9993b0d3fa..3b32d3c2209 100644 --- a/python/GafferUI/GadgetWidget.py +++ b/python/GafferUI/GadgetWidget.py @@ -291,6 +291,11 @@ def __wheel( self, widget, event ) : if not self._makeCurrent() : return False + # We get given wheel events before they're given to the overlay items, + # so we must ignore them so they can be used by the overlay. + if self._qtWidget().itemAt( event.line.p0.x, event.line.p0.y ) is not None : + return False + return self.__viewportGadget.wheelSignal()( self.__viewportGadget, event ) def __visibilityChanged( self, widget ) : diff --git a/python/GafferUI/PathListingWidget.py b/python/GafferUI/PathListingWidget.py index 265c360fe0b..92d9de67269 100644 --- a/python/GafferUI/PathListingWidget.py +++ b/python/GafferUI/PathListingWidget.py @@ -187,6 +187,17 @@ def pathAt( self, position ) : return self.__pathForIndex( index ) + ## Returns the column being displayed at the specified + # position within the widget. + def columnAt( self, position ) : + + point = self._qtWidget().viewport().mapFrom( + self._qtWidget(), + QtCore.QPoint( position.x, position.y ) + ) + + return self.getColumns()[self._qtWidget().columnAt( point.x())] + ## Sets which paths are currently expanded # using an `IECore.PathMatcher` object. def setExpansion( self, paths ) : @@ -581,7 +592,7 @@ def __buttonPress( self, widget, event ) : if not selected : self._qtWidget().setCurrentIndex( index ) - self.setSelection( IECore.PathMatcher( [ path ] ) ) + self.setSelection( IECore.PathMatcher( [ path ] ), scrollToFirst=False, expandNonLeaf=False ) return True # The item is selected, Return True so that we have the option of diff --git a/python/GafferUITest/GraphGadgetTest.py b/python/GafferUITest/GraphGadgetTest.py index 270e1063657..c00377e671a 100644 --- a/python/GafferUITest/GraphGadgetTest.py +++ b/python/GafferUITest/GraphGadgetTest.py @@ -1631,6 +1631,18 @@ def testActivePlugsAndNodes( self ) : self.assertEqual( set( nodes ), set( [ box, s["add2"], box["BoxOut1"]["__switch"], box["BoxOut1"] ] ) ) + # Test a box with promoted array plug + s["add7"] = GafferTest.AddNode() + s["add1"]["op1"].setInput( s["add7"]["sum"] ) + box2 = Gaffer.Box.create( s, Gaffer.StandardSet( [ s["add7"] ] ) ) + box2["add7"]["arrayInput"] = Gaffer.ArrayPlug( "arrayInput", Gaffer.Plug.Direction.In, Gaffer.FloatPlug() ) + box2.promotePlug( box2["add7"]["arrayInput"] ) + box2["arrayInput"][0].setInput( s["add5"]["sum"] ) + box2["arrayInput"][1].setInput( s["add6"]["sum"] ) + plugs, nodes = GafferUI.GraphGadget._activePlugsAndNodes( s["add1"]["sum"], c ) + self.assertEqual( set( plugs ), set( [ s["add1"]["op1"], box2["sum"], box2["add7"]["arrayInput"], box2["arrayInput"][0], box2["arrayInput"][1] ] ) ) + self.assertEqual( set( nodes ), set( [ s["add1"], box2, box2["add7"], s["add5"], s["add6"] ] ) ) + # Test Loop s["loopSwitch"] = Gaffer.Switch() diff --git a/src/GafferOSL/OSLCode.cpp b/src/GafferOSL/OSLCode.cpp index 27f96cb4b2a..3c9e0e75f21 100644 --- a/src/GafferOSL/OSLCode.cpp +++ b/src/GafferOSL/OSLCode.cpp @@ -306,10 +306,26 @@ boost::filesystem::path compile( const std::string &shaderName, const std::strin } } + if( !boost::filesystem::file_size( tempOSLFileName ) ) + { + // Belt and braces. `compiler.compile()` should be reporting all errors, + // but on rare occasions we have still seen empty `.oso` files being + // produced. Detect this and warn so we can get to the bottom of it. + throw IECore::Exception( "Empty file after compilation : \"" + tempOSLFileName + "\"" ); + } + // Move temp file where we really want it, and clean up. boost::filesystem::rename( tempOSOFileName, osoFileName ); + if( !boost::filesystem::file_size( osoFileName ) ) + { + // Belt and braces. `rename()` should be reporting all errors, + // but on rare occasions we have still seen empty `.oso` files being + // produced. Detect this and warn so we can get to the bottom of it. + throw IECore::Exception( "Empty file after rename : \"" + osoFileName.string() + "\"" ); + } + return osoFileName; } diff --git a/src/GafferScene/BranchCreator.cpp b/src/GafferScene/BranchCreator.cpp index cc4f10239ff..0e88dc1e89b 100644 --- a/src/GafferScene/BranchCreator.cpp +++ b/src/GafferScene/BranchCreator.cpp @@ -1218,13 +1218,13 @@ BranchCreator::LocationType BranchCreator::sourceAndBranchPaths( const ScenePath } } - if( location->children.empty() ) + if( path.size() == location->depth && !location->children.empty() ) { - return PassThrough; + return location->exists ? Ancestor : NewAncestor; } else { - return location->exists ? Ancestor : NewAncestor; + return PassThrough; } } diff --git a/src/GafferUI/GraphGadget.cpp b/src/GafferUI/GraphGadget.cpp index 990623dee41..7e65de892b9 100644 --- a/src/GafferUI/GraphGadget.cpp +++ b/src/GafferUI/GraphGadget.cpp @@ -310,7 +310,9 @@ void activeWalkOutput( { if( connectionStart->direction() != Gaffer::Plug::Direction::Out ) { - // An input plug with no input connections isn't affected by anything + // The only possible connections to an input plug with no input connections is if its + // children are connected + activeWalkInput( connectionStart, true, context, canceller, activePlugs, activeNodes, plugContextsVisited ); return; } } diff --git a/src/GafferUI/StandardNodeGadget.cpp b/src/GafferUI/StandardNodeGadget.cpp index 78fc1fb6d7a..a56afebe4ce 100644 --- a/src/GafferUI/StandardNodeGadget.cpp +++ b/src/GafferUI/StandardNodeGadget.cpp @@ -208,6 +208,11 @@ class FocusGadget : public Gadget if( g_pendingHoveredFocus ) { StandardNodeGadget* parentNodeGadget = static_cast( g_pendingHoveredFocus->parent() ); + if( !parentNodeGadget ) + { + IECore::msg( IECore::Msg::Error, "FocusGadget::nodeMouseEntered", "Focus gadget hover timer triggered on unparented FocusGadget" ); + return; + } g_hoveredFocus = g_pendingHoveredFocus; g_hoveredFocusNodePosition = parentNodeGadget->getTransform(); g_pendingHoveredFocus->dirty( DirtyType::Render ); diff --git a/src/IECoreArnold/Renderer.cpp b/src/IECoreArnold/Renderer.cpp index be77835eafe..3e935d97d32 100644 --- a/src/IECoreArnold/Renderer.cpp +++ b/src/IECoreArnold/Renderer.cpp @@ -3106,6 +3106,7 @@ class ArnoldGlobals // Delete nodes we own before universe is destroyed. m_shaderCache.reset(); m_outputs.clear(); + m_aovShaders.clear(); m_colorManager.reset(); m_atmosphere.reset(); m_background.reset();