Skip to content

Commit

Permalink
Merge pull request #5701 from johnhaddon/extensionAlgoBuild
Browse files Browse the repository at this point in the history
SConstruct : Export node definitions using ExtensionAlgo
  • Loading branch information
johnhaddon authored Mar 5, 2024
2 parents 4cbed38 + 2a2da12 commit 84cdfd2
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 154 deletions.
5 changes: 5 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ API
- ScenePath : Added automatic conversion of a list of Python strings to a ScenePath [^1].
- RenderPassEditor : Added `registerPathGroupingFunction()` and `pathGroupingFunction()` methods [^1].

API
---

- ExtensionAlgo : Added `exportNode()` and `exportNodeUI()` functions.

Breaking Changes
----------------

Expand Down
85 changes: 73 additions & 12 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import locale
import platform
import shutil
import subprocess
import tempfile
import distutils.dir_util
import codecs

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -1737,35 +1739,45 @@ 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

for additionalFile in libraryDef.get( "additionalFiles", [] ) :
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

Expand All @@ -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

Expand All @@ -1807,7 +1819,56 @@ 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", delete = False ) 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}'], r'{targetFile}' )\n" )
exportScript.write( f"Gaffer.ExtensionAlgo.exportNodeUI( '{moduleName}', script['{nodeName}'], r'{targetUIFile}' )\n" )

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" )
env.Alias( "buildExtensions", exportedFiles )
env.Alias( "build", "buildExtensions" )

#########################################################################################################
# Graphics
Expand Down
33 changes: 19 additions & 14 deletions python/Gaffer/ExtensionAlgo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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"
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -173,30 +178,30 @@ 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}
)
"""

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 )
Expand Down
96 changes: 96 additions & 0 deletions python/GafferImage/Anaglyph.gfr
Original file line number Diff line number Diff line change
@@ -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

Loading

0 comments on commit 84cdfd2

Please sign in to comment.