From 341cdff47a896fcf55a78c23edf499e25c4c8efb Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 27 Dec 2023 11:27:01 +0000 Subject: [PATCH 1/2] SConstruct : Export node definitions using ExtensionAlgo And convert Anaglyph node to demonstrate this in action. There is a slight annoyance here in that the extensions depend on the whole of `buildCore`, because we need to be able to run Gaffer to export them. Which means they get re-exported when anything changes. This is pretty fast anyway, but it can be avoided around by using `scons buildCore` rather than `scons build` when you know extensions don't need to be exported again. --- Changes.md | 5 ++ SConstruct | 76 +++++++++++++++++++---- python/Gaffer/ExtensionAlgo.py | 33 +++++----- python/GafferImage/Anaglyph.gfr | 96 ++++++++++++++++++++++++++++++ python/GafferImage/Anaglyph.py | 76 ----------------------- python/GafferImageUI/AnaglyphUI.py | 52 ---------------- 6 files changed, 184 insertions(+), 154 deletions(-) create mode 100644 python/GafferImage/Anaglyph.gfr delete mode 100644 python/GafferImage/Anaglyph.py delete mode 100644 python/GafferImageUI/AnaglyphUI.py diff --git a/Changes.md b/Changes.md index 1061750d3de..8add3999a89 100644 --- a/Changes.md +++ b/Changes.md @@ -7,6 +7,11 @@ Fixes - 3Delight : Fixed startup errors on Windows when the `DELIGHT` environment variable wasn't defined [^1]. - FlatImageProcessor : Fixed bug that could cause an input to be evaluated with an invalid `image:viewName`. +API +--- + +- ExtensionAlgo : Added `exportNode()` and `exportNodeUI()` functions. + Breaking Changes ---------------- diff --git a/SConstruct b/SConstruct index 7563f57f2e5..5c71169ced5 100644 --- a/SConstruct +++ b/SConstruct @@ -44,6 +44,7 @@ import locale import platform import shutil import subprocess +import tempfile import distutils.dir_util import codecs @@ -400,7 +401,6 @@ env = Environment( ) - # include 3rd party headers with -isystem rather than -I. # this should turn off warnings from those headers, allowing us to # build with -Werror. there are so many warnings from boost @@ -1590,6 +1590,8 @@ if env["PLATFORM"] == "win32" : # The stuff that actually builds the libraries and python modules ############################################################################################### +extensionSources = [] +extensionTargets = [] for libraryName, libraryDef in libraries.items() : # skip this library if we don't have the config we need @@ -1627,7 +1629,7 @@ for libraryName, libraryDef in libraries.items() : os.path.join( installRoot, os.path.dirname( libraryInstallName ) ), library ) - libEnv.Alias( "build", libraryInstall ) + libEnv.Alias( "buildCore", libraryInstall ) # header install @@ -1680,7 +1682,7 @@ for libraryName, libraryDef in libraries.items() : bindingsEnv.Default( bindingsLibrary ) bindingsLibraryInstall = bindingsEnv.Install( os.path.join( installRoot, "lib" ), bindingsLibrary ) - env.Alias( "build", bindingsLibraryInstall ) + env.Alias( "buildCore", bindingsLibraryInstall ) # bindings header install @@ -1715,7 +1717,7 @@ for libraryName, libraryDef in libraries.items() : pythonModuleEnv.Default( pythonModule ) moduleInstall = pythonModuleEnv.Install( os.path.join( installRoot, "python", libraryName ), pythonModule ) - pythonModuleEnv.Alias( "build", moduleInstall ) + pythonModuleEnv.Alias( "buildCore", moduleInstall ) # Moc preprocessing, for QObject derived classes. SCons does include a "qt" tool that # can scan files automatically for the Q_OBJECT macro, but it hasn't been updated for Qt 5. @@ -1737,20 +1739,30 @@ for libraryName, libraryDef in libraries.items() : pythonFile, SUBST_DICT = fileSubstitutions ) - env.Alias( "build", pythonFileInstall ) + env.Alias( "buildCore", pythonFileInstall ) + + # Nodes implemented using ExtensionAlgo. + + for extensionSource in glob.glob( "python/" + libraryName + "/*.gfr" ) : + extensionSources.append( extensionSource ) + extensionNode = os.path.splitext( os.path.basename( extensionSource ) )[0] + extensionTargets.extend( [ + os.path.join( installRoot, "python", libraryName, extensionNode + ".py" ), + os.path.join( installRoot, "python", libraryName + "UI", extensionNode + "UI.py" ), + ] ) # apps for app in libraryDef.get( "apps", [] ) : appInstall = env.InstallAs( os.path.join( installRoot, "apps", app, "{app}-1.py".format( app=app ) ), "apps/{app}/{app}-1.py".format( app=app ) ) - env.Alias( "build", appInstall ) + env.Alias( "buildCore", appInstall ) # startup files for startupDir in libraryDef.get( "apps", [] ) + [ libraryName ] : for startupFile in glob.glob( "startup/{startupDir}/*.py".format( startupDir=startupDir ) ) + glob.glob( "startup/{startupDir}/*.gfr".format( startupDir=startupDir ) ) : startupFileInstall = env.InstallAs( os.path.join( installRoot, startupFile ), startupFile ) - env.Alias( "build", startupFileInstall ) + env.Alias( "buildCore", startupFileInstall ) # additional files @@ -1758,14 +1770,14 @@ for libraryName, libraryDef in libraries.items() : if additionalFile in pythonFiles : continue additionalFileInstall = env.InstallAs( os.path.join( installRoot, additionalFile ), additionalFile ) - env.Alias( "build", additionalFileInstall ) + env.Alias( "buildCore", additionalFileInstall ) # osl headers for oslHeader in libraryDef.get( "oslHeaders", [] ) : oslHeaderInstall = env.InstallAs( os.path.join( installRoot, oslHeader ), oslHeader ) env.Alias( "oslHeaders", oslHeaderInstall ) - env.Alias( "build", oslHeaderInstall ) + env.Alias( "buildCore", oslHeaderInstall ) # osl shaders @@ -1781,10 +1793,10 @@ for libraryName, libraryDef in libraries.items() : ) for oslShader in libraryDef.get( "oslShaders", [] ) : - env.Alias( "build", oslShader ) + env.Alias( "buildCore", oslShader ) compiledFile = commandEnv.Command( os.path.join( installRoot, os.path.splitext( oslShader )[0] + ".oso" ), oslShader, buildOSL ) env.Depends( compiledFile, "oslHeaders" ) - env.Alias( "build", compiledFile ) + env.Alias( "buildCore", compiledFile ) # class stubs @@ -1807,7 +1819,47 @@ for libraryName, libraryDef in libraries.items() : GAFFER_STUB_CLASS = classStub[0], ) stub = stubEnv.Command( stubFileName, "", buildClassStub ) - stubEnv.Alias( "build", stub ) + stubEnv.Alias( "buildCore", stub ) + +env.Alias( "build", "buildCore" ) + +######################################################################################################### +# Python nodes authored as Boxes and exported by ExtensionAlgo +######################################################################################################### + +def exportExtensions( target, source, env ) : + + with tempfile.NamedTemporaryFile( "w" ) as exportScript : + + exportScript.write( "import Gaffer\nscript = Gaffer.ScriptNode()\n" ) + for sourceFile, targetFile, targetUIFile in zip( source, target[::2], target[1::2] ) : + + sourceFile = str( sourceFile ) + targetFile = str( targetFile ) + targetUIFile = str( targetUIFile ) + moduleName = os.path.basename( os.path.dirname( sourceFile ) ) + nodeName = os.path.splitext( os.path.basename( sourceFile ) )[0] + + # We have a chicken and egg situation. We need to import the Gaffer modules + # to be able to do the export, but their `__init__.py` files will be wanting + # to import the extensions that we haven't created yet. Write stub files + # to allow the imports to go ahead. + if not os.path.exists( targetFile ) : + with open( targetFile, "w" ) as stub : + stub.write( f"class {nodeName} : pass\n" ) + + exportScript.write( f"\nscript['fileName'].setValue( '{sourceFile}' )\n" ) + exportScript.write( "script.load()\n" ) + exportScript.write( f"Gaffer.ExtensionAlgo.exportNode( '{moduleName}', script['{nodeName}'], '{targetFile}' )\n" ) + exportScript.write( f"Gaffer.ExtensionAlgo.exportNodeUI( '{moduleName}', script['{nodeName}'], '{targetUIFile}' )\n" ) + + exportScript.flush() + subprocess.check_call( [ "gaffer", "env", "python", exportScript.name ], env = env["ENV"] ) + +exportedFiles = commandEnv.Command( extensionTargets, extensionSources, exportExtensions ) +env.Depends( exportedFiles, "buildCore" ) +env.Alias( "buildExtensions", exportedFiles ) +env.Alias( "build", "buildExtensions" ) ######################################################################################################### # Graphics diff --git a/python/Gaffer/ExtensionAlgo.py b/python/Gaffer/ExtensionAlgo.py index 66013576832..59fd560dc09 100644 --- a/python/Gaffer/ExtensionAlgo.py +++ b/python/Gaffer/ExtensionAlgo.py @@ -57,10 +57,7 @@ def exportExtension( name, boxes, directory ) : with open( pythonDir / "__init__.py", "w", encoding = "utf-8" ) as initFile : for box in boxes : - - with open( pythonDir / (box.getName() + ".py"), "w", encoding = "utf-8" ) as nodeFile : - nodeFile.write( __nodeDefinition( box, name ) ) - + exportNode( name, box, pythonDir / (box.getName() + ".py") ) initFile.write( "from .{name} import {name}\n".format( name = box.getName() ) ) uiDir = directory / "python" / (name + "UI") @@ -70,9 +67,7 @@ def exportExtension( name, boxes, directory ) : for box in boxes : - with open( uiDir / (box.getName() + "UI.py"), "w", encoding = "utf-8" ) as uiFile : - uiFile.write( __uiDefinition( box, name ) ) - + exportNodeUI( name, box, uiDir / (box.getName() + "UI.py" ) ) initFile.write( "from . import {name}UI\n".format( name = box.getName() ) ) startupDir = directory / "startup" / "gui" @@ -102,6 +97,16 @@ def exportExtension( name, boxes, directory ) : ) ) +def exportNode( moduleName, box, fileName ) : + + with open( fileName, "w", encoding = "utf-8" ) as nodeFile : + nodeFile.write( __nodeDefinition( box, moduleName ) ) + +def exportNodeUI( moduleName, box, fileName ) : + + with open( fileName, "w", encoding = "utf-8" ) as uiFile : + uiFile.write( __uiDefinition( box, moduleName ) ) + __startupTemplate = """\ import GafferUI import {name} @@ -140,10 +145,10 @@ def __removeDynamicFlags( self ) : for plug in Gaffer.Plug.RecursiveRange( plug ) : plug.setFlags( Gaffer.Plug.Flags.Dynamic, False ) -IECore.registerRunTimeTyped( {name}, typeName = "{extension}::{name}" ) +IECore.registerRunTimeTyped( {name}, typeName = "{moduleName}::{name}" ) """ -def __nodeDefinition( box, extension ) : +def __nodeDefinition( box, moduleName ) : invisiblePlug = re.compile( "^__.*$" ) children = Gaffer.StandardSet() @@ -173,18 +178,18 @@ def __nodeDefinition( box, extension ) : imports = "\n".join( sorted( imports ) ), name = box.getName(), constructor = __indent( "\n".join( constructorLines ), 2 ), - extension = extension + moduleName = moduleName ) __uiTemplate = """\ import imath import IECore import Gaffer -import {extension} +import {moduleName} Gaffer.Metadata.registerNode( - {extension}.{name}, + {moduleName}.{name}, {metadata} {plugMetadata} @@ -192,11 +197,11 @@ def __nodeDefinition( box, extension ) : ) """ -def __uiDefinition( box, extension ) : +def __uiDefinition( box, moduleName ) : return __uiTemplate.format( - extension = extension, + moduleName = moduleName, name = box.getName(), metadata = __indent( __metadata( box ), 1 ), plugMetadata = __indent( __plugMetadata( box ), 1 ) diff --git a/python/GafferImage/Anaglyph.gfr b/python/GafferImage/Anaglyph.gfr new file mode 100644 index 00000000000..4d0db19790e --- /dev/null +++ b/python/GafferImage/Anaglyph.gfr @@ -0,0 +1,96 @@ +import Gaffer +import GafferImage +import imath + +Gaffer.Metadata.registerValue( parent, "serialiser:milestoneVersion", 1, persistent=False ) +Gaffer.Metadata.registerValue( parent, "serialiser:majorVersion", 4, persistent=False ) +Gaffer.Metadata.registerValue( parent, "serialiser:minorVersion", 0, persistent=False ) +Gaffer.Metadata.registerValue( parent, "serialiser:patchVersion", 0, persistent=False ) + +__children = {} + +parent["variables"].addChild( Gaffer.NameValuePlug( "image:catalogue:port", Gaffer.IntPlug( "value", defaultValue = 0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "imageCataloguePort", Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) +parent["variables"].addChild( Gaffer.NameValuePlug( "project:name", Gaffer.StringPlug( "value", defaultValue = 'default', flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "projectName", Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) +parent["variables"].addChild( Gaffer.NameValuePlug( "project:rootDirectory", Gaffer.StringPlug( "value", defaultValue = '$HOME/gaffer/projects/${project:name}', flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), "projectRootDirectory", Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) +__children["openColorIO"] = GafferImage.OpenColorIOConfigPlug( "openColorIO", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) +parent.addChild( __children["openColorIO"] ) +__children["defaultFormat"] = GafferImage.FormatPlug( "defaultFormat", defaultValue = GafferImage.Format( 1920, 1080, 1.000 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) +parent.addChild( __children["defaultFormat"] ) +__children["Anaglyph"] = Gaffer.Box( "Anaglyph" ) +parent.addChild( __children["Anaglyph"] ) +__children["Anaglyph"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( GafferImage.SelectView( "__SelectLeft" ) ) +__children["Anaglyph"]["__SelectLeft"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( GafferImage.DeleteChannels( "__DeleteChannelsLeft" ) ) +__children["Anaglyph"]["__DeleteChannelsLeft"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( GafferImage.SelectView( "__SelectRight" ) ) +__children["Anaglyph"]["__SelectRight"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( GafferImage.DeleteChannels( "__DeleteChannelsRight" ) ) +__children["Anaglyph"]["__DeleteChannelsRight"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( GafferImage.Merge( "__Merge" ) ) +__children["Anaglyph"]["__Merge"]["in"].addChild( GafferImage.ImagePlug( "in2", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"]["__Merge"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( Gaffer.BoxIn( "BoxIn" ) ) +__children["Anaglyph"]["BoxIn"].setup( GafferImage.ImagePlug( "out", ) ) +__children["Anaglyph"]["BoxIn"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( GafferImage.ImagePlug( "in", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( Gaffer.BoolPlug( "enabled", defaultValue = True, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( Gaffer.BoxOut( "BoxOut" ) ) +__children["Anaglyph"]["BoxOut"].setup( GafferImage.ImagePlug( "in", ) ) +__children["Anaglyph"]["BoxOut"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( GafferImage.ImagePlug( "out", direction = Gaffer.Plug.Direction.Out, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( Gaffer.Dot( "Dot" ) ) +__children["Anaglyph"]["Dot"].setup( GafferImage.ImagePlug( "in", ) ) +__children["Anaglyph"]["Dot"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Anaglyph"].addChild( Gaffer.Dot( "Dot1" ) ) +__children["Anaglyph"]["Dot1"].setup( GafferImage.ImagePlug( "in", ) ) +__children["Anaglyph"]["Dot1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +parent["variables"]["imageCataloguePort"]["value"].setValue( 52733 ) +Gaffer.Metadata.registerValue( parent["variables"]["imageCataloguePort"], 'readOnly', True ) +Gaffer.Metadata.registerValue( parent["variables"]["projectName"]["name"], 'readOnly', True ) +Gaffer.Metadata.registerValue( parent["variables"]["projectRootDirectory"]["name"], 'readOnly', True ) +Gaffer.Metadata.registerValue( __children["Anaglyph"], 'description', 'Converts a multi-view image with "left" and "right" views into a single view image with the two views combined in different colors, suitable for viewing through red-blue anaglyph glasses.' ) +__children["Anaglyph"]["__uiPosition"].setValue( imath.V2f( -6.14999914, -0.599999785 ) ) +__children["Anaglyph"]["__SelectLeft"]["in"].setInput( __children["Anaglyph"]["BoxIn"]["out"] ) +__children["Anaglyph"]["__SelectLeft"]["__uiPosition"].setValue( imath.V2f( -18.9706993, 9.24895382 ) ) +__children["Anaglyph"]["__DeleteChannelsLeft"]["in"].setInput( __children["Anaglyph"]["__SelectLeft"]["out"] ) +__children["Anaglyph"]["__DeleteChannelsLeft"]["channels"].setValue( '[GB] *.[GB]' ) +__children["Anaglyph"]["__DeleteChannelsLeft"]["__uiPosition"].setValue( imath.V2f( -18.9708977, 1.08303797 ) ) +__children["Anaglyph"]["__SelectRight"]["in"].setInput( __children["Anaglyph"]["BoxIn"]["out"] ) +__children["Anaglyph"]["__SelectRight"]["view"].setValue( 'right' ) +__children["Anaglyph"]["__SelectRight"]["__uiPosition"].setValue( imath.V2f( -1.52422142, 9.24435806 ) ) +__children["Anaglyph"]["__DeleteChannelsRight"]["in"].setInput( __children["Anaglyph"]["__SelectRight"]["out"] ) +__children["Anaglyph"]["__DeleteChannelsRight"]["channels"].setValue( '[R] *.[R]' ) +__children["Anaglyph"]["__DeleteChannelsRight"]["__uiPosition"].setValue( imath.V2f( -1.52412033, 1.08029604 ) ) +__children["Anaglyph"]["__Merge"]["in"][0].setInput( __children["Anaglyph"]["__DeleteChannelsLeft"]["out"] ) +__children["Anaglyph"]["__Merge"]["in"][1].setInput( __children["Anaglyph"]["__DeleteChannelsRight"]["out"] ) +__children["Anaglyph"]["__Merge"]["operation"].setValue( 13 ) +__children["Anaglyph"]["__Merge"]["__uiPosition"].setValue( imath.V2f( -8.74757004, -7.08376598 ) ) +__children["Anaglyph"]["BoxIn"]["__in"].setInput( __children["Anaglyph"]["in"] ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["BoxIn"]["__in"], 'nodule:type', 'GafferUI::StandardNodule' ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["BoxIn"]["__in"], 'description', 'The input image' ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["BoxIn"]["__in"], 'plugValueWidget:type', '' ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["BoxIn"]["__in"], 'noduleLayout:spacing', 2.0 ) +__children["Anaglyph"]["BoxIn"]["__uiPosition"].setValue( imath.V2f( -10.3432178, 25.673069 ) ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["in"], 'nodule:type', 'GafferUI::StandardNodule' ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["in"], 'description', 'The input image' ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["in"], 'plugValueWidget:type', '' ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["in"], 'noduleLayout:spacing', 2.0 ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["enabled"], 'nodule:type', '' ) +__children["Anaglyph"]["BoxOut"]["in"].setInput( __children["Anaglyph"]["__Merge"]["out"] ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["BoxOut"]["__out"], 'nodule:type', 'GafferUI::StandardNodule' ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["BoxOut"]["__out"], 'description', 'The output image generated by this node.' ) +__children["Anaglyph"]["BoxOut"]["passThrough"].setInput( __children["Anaglyph"]["Dot"]["out"] ) +__children["Anaglyph"]["BoxOut"]["enabled"].setInput( __children["Anaglyph"]["enabled"] ) +__children["Anaglyph"]["BoxOut"]["__uiPosition"].setValue( imath.V2f( -7.24757004, -21.0223522 ) ) +__children["Anaglyph"]["out"].setInput( __children["Anaglyph"]["BoxOut"]["__out"] ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["out"], 'nodule:type', 'GafferUI::StandardNodule' ) +Gaffer.Metadata.registerValue( __children["Anaglyph"]["out"], 'description', 'The output image generated by this node.' ) +__children["Anaglyph"]["Dot"]["in"].setInput( __children["Anaglyph"]["Dot1"]["out"] ) +__children["Anaglyph"]["Dot"]["__uiPosition"].setValue( imath.V2f( 13.4827852, -13.5223522 ) ) +__children["Anaglyph"]["Dot1"]["in"].setInput( __children["Anaglyph"]["BoxIn"]["out"] ) +__children["Anaglyph"]["Dot1"]["__uiPosition"].setValue( imath.V2f( 13.4827852, 18.173069 ) ) + + +del __children + diff --git a/python/GafferImage/Anaglyph.py b/python/GafferImage/Anaglyph.py deleted file mode 100644 index b9caba1dcca..00000000000 --- a/python/GafferImage/Anaglyph.py +++ /dev/null @@ -1,76 +0,0 @@ -########################################################################## -# -# Copyright (c) 2022, 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 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 IECore - -import Gaffer -import GafferImage - -class Anaglyph( GafferImage.ImageProcessor ) : - - def __init__(self, name = 'Anaglyph' ) : - GafferImage.ImageProcessor.__init__( self, name ) - - self["__SelectLeft"] = GafferImage.SelectView() - self["__SelectLeft"]["in"].setInput( self["in"] ) - - self["__DeleteChannelsLeft"] = GafferImage.DeleteChannels() - self["__DeleteChannelsLeft"]["in"].setInput( self["__SelectLeft"]["out"] ) - self["__DeleteChannelsLeft"]["channels"].setValue( '[GB] *.[GB]' ) - - self["__SelectRight"] = GafferImage.SelectView() - self["__SelectRight"]["in"].setInput( self["in"] ) - self["__SelectRight"]["view"].setValue( 'right' ) - - self["__DeleteChannelsRight"] = GafferImage.DeleteChannels() - self["__DeleteChannelsRight"]["in"].setInput( self["__SelectRight"]["out"] ) - self["__DeleteChannelsRight"]["channels"].setValue( '[R] *.[R]' ) - - self["__Merge"] = GafferImage.Merge() - self["__Merge"]["in"][0].setInput( self["__DeleteChannelsLeft"]["out"] ) - self["__Merge"]["in"][1].setInput( self["__DeleteChannelsRight"]["out"] ) - self["__Merge"]["operation"].setValue( GafferImage.Merge.Operation.Max ) - - self["__disableSwitch"] = Gaffer.Switch() - self["__disableSwitch"].setup( self["in"] ) - self["__disableSwitch"]["in"][0].setInput( self["in"] ) - self["__disableSwitch"]["in"][1].setInput( self["__Merge"]["out"] ) - self["__disableSwitch"]["index"].setInput( self["enabled"] ) - - self['out'].setFlags(Gaffer.Plug.Flags.Serialisable, False) - self["out"].setInput( self["__disableSwitch"]["out"] ) - -IECore.registerRunTimeTyped( Anaglyph, typeName = "GafferImage::Anaglyph" ) diff --git a/python/GafferImageUI/AnaglyphUI.py b/python/GafferImageUI/AnaglyphUI.py deleted file mode 100644 index 5900e3e5e57..00000000000 --- a/python/GafferImageUI/AnaglyphUI.py +++ /dev/null @@ -1,52 +0,0 @@ -########################################################################## -# -# Copyright (c) 2022, 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 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 GafferImage - -Gaffer.Metadata.registerNode( - - GafferImage.Anaglyph, - - "description", - """ - Converts a multi-view image with "left" and "right" views - into a single view image with the two views combined in - different colors, suitable for viewing through red-blue - anaglyph glasses. - """, - -) From 2a2da1288c579fff83e530724e08985ddb2b53c5 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 1 Mar 2024 16:34:37 +0000 Subject: [PATCH 2/2] SConstruct : Fix ExtensionAlgo export on Windows - We can't let `NamedTemporaryFile` delete the file for us, because the file needs to be closed for us to be able to open it in `gaffer env python` on Windows, and calling `close()` also deletes. We can do better in Python 3.12 by passing `close_on_delete = False` and then letting the context manager delete the file on exit. - We need to output file paths into the `exportScript` as raw strings, so that Windows `\` characters don't get interpreted as escape characters. - We need to specify the full path to `gaffer.cmd` in `check_call()`. --- SConstruct | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/SConstruct b/SConstruct index 5c71169ced5..c79b4e695af 100644 --- a/SConstruct +++ b/SConstruct @@ -1829,7 +1829,7 @@ env.Alias( "build", "buildCore" ) def exportExtensions( target, source, env ) : - with tempfile.NamedTemporaryFile( "w" ) as exportScript : + with tempfile.NamedTemporaryFile( "w", delete = False ) as exportScript : exportScript.write( "import Gaffer\nscript = Gaffer.ScriptNode()\n" ) for sourceFile, targetFile, targetUIFile in zip( source, target[::2], target[1::2] ) : @@ -1850,11 +1850,20 @@ def exportExtensions( target, source, env ) : exportScript.write( f"\nscript['fileName'].setValue( '{sourceFile}' )\n" ) exportScript.write( "script.load()\n" ) - exportScript.write( f"Gaffer.ExtensionAlgo.exportNode( '{moduleName}', script['{nodeName}'], '{targetFile}' )\n" ) - exportScript.write( f"Gaffer.ExtensionAlgo.exportNodeUI( '{moduleName}', script['{nodeName}'], '{targetUIFile}' )\n" ) + exportScript.write( f"Gaffer.ExtensionAlgo.exportNode( '{moduleName}', script['{nodeName}'], r'{targetFile}' )\n" ) + exportScript.write( f"Gaffer.ExtensionAlgo.exportNodeUI( '{moduleName}', script['{nodeName}'], r'{targetUIFile}' )\n" ) - exportScript.flush() - subprocess.check_call( [ "gaffer", "env", "python", exportScript.name ], env = env["ENV"] ) + exportScript.close() + + subprocess.check_call( + [ + shutil.which( "gaffer.cmd" if sys.platform == "win32" else "gaffer", path = env["ENV"]["PATH"] ), + "env", "python", exportScript.name + ], + env = env["ENV"] + ) + + os.unlink( exportScript.name ) exportedFiles = commandEnv.Command( extensionTargets, extensionSources, exportExtensions ) env.Depends( exportedFiles, "buildCore" )