Scripting Box Nodes: Difference between revisions
mNo edit summary |
No edit summary |
||
Line 3: | Line 3: | ||
[[File:ScriptingBoxNodes1.png]] | [[File:ScriptingBoxNodes1.png]] | ||
The Gaffer docs have a page covering [http://www.gafferhq.org/documentation/ | The Gaffer docs have a page covering [http://www.gafferhq.org/documentation/{{latestGafferVersion}}/WorkingWithTheNodeGraph/BoxNode/index.html Box nodes and how to use them]. In this guide we take a quick look at how to re-create a simple GroundPlane tool made in the UI, using only Gaffer’s python API. | ||
{{ warning | This guide assumes you are familiar with working with Box nodes in the UI, including [http://www.gafferhq.org/documentation/ | {{ warning | This guide assumes you are familiar with working with Box nodes in the UI, including [http://www.gafferhq.org/documentation/{{latestGafferVersion}}/WorkingWithTheNodeGraph/BoxNode/index.html#promoting-and-demoting-a-plug promoting plugs] and how [http://www.gafferhq.org/documentation/{{latestGafferVersion}}/WorkingWithTheNodeGraph/BoxNode/index.html#connecting-a-box-to-the-main-graph in/out nodes work]. You also need to be comfortable with using [http://www.gafferhq.org/documentation/{{latestGafferVersion}}/WorkingWithThePythonScriptingAPI/TutorialNodeGraphEditingInPython/index.html Gaffer’s Python API to work with the node graph]. | ||
}} | }} | ||
Line 58: | Line 58: | ||
=== Promoting plugs === | === Promoting plugs === | ||
We now have our network inside the box, but the box has no input or output. We can use the Gaffer.BoxIO node’s static promote method to promote the in/out plugs on the Parent node, as a user might do using the right-click menus in the UI: | We now have our network inside the box, but the box has no input or output. We can use the Gaffer.BoxIO node’s static promote method to promote the in/out plugs on the Parent node, as a user might do using the right-click menus in the UI: | ||
<syntaxhighlight lang="python"> | |||
Gaffer.BoxIO.promote( parent["in"] ) | Gaffer.BoxIO.promote( parent["in"] ) | ||
Gaffer.BoxIO.promote( parent["out"] ) | Gaffer.BoxIO.promote( parent["out"] ) | ||
</syntaxhighlight> | |||
You can use exactly the same function to promote plugs that appear in the Node Editor instead of in the Graph Editor (they’re all just plugs after all). BoxIO.promote preserves the plugs existing metadata, which is what controls where they’re presented, so it knows whether they should be on the top or bottom or sides of the box. | You can use exactly the same function to promote plugs that appear in the Node Editor instead of in the Graph Editor (they’re all just plugs after all). BoxIO.promote preserves the plugs existing metadata, which is what controls where they’re presented, so it knows whether they should be on the top or bottom or sides of the box. | ||
<syntaxhighlight lang="python"> | |||
Gaffer.BoxIO.promote( plane["name"] ) | Gaffer.BoxIO.promote( plane["name"] ) | ||
Gaffer.BoxIO.promote( parent["parent"] ) | Gaffer.BoxIO.promote( parent["parent"] ) | ||
Gaffer.BoxIO.promote( plane["divisions"] ) | Gaffer.BoxIO.promote( plane["divisions"] ) | ||
Gaffer.BoxIO.promote( plane["dimensions"] ) | Gaffer.BoxIO.promote( plane["dimensions"] ) | ||
</syntaxhighlight> | |||
The promote() method takes care of creating, connecting and configuring BoxIn/ BoxOut nodes and plugs on the Box itself. You don’t need to manually manage these. | The promote() method takes care of creating, connecting and configuring BoxIn/ BoxOut nodes and plugs on the Box itself. You don’t need to manually manage these. | ||
Line 70: | Line 74: | ||
=== Disabling the node (a.k.a finding inputs and outputs). === | === Disabling the node (a.k.a finding inputs and outputs). === | ||
In order to allow a user to disable/bypass the Box, we need to provide a pass-through connection ([http://www.gafferhq.org/documentation/ | In order to allow a user to disable/bypass the Box, we need to provide a pass-through connection ([http://www.gafferhq.org/documentation/{{latestGafferVersion}}/WorkingWithTheNodeGraph/BoxNode/index.html#setting-up-a-box-for-pass-through explained here]) that defines what to output when the Box is disabled. To do this, we’ll need to get the BoxIn and BoxOut nodes so we can connect the boxOutNode["passThrough"] plug to the boxInNode["out"] plug. | ||
{{ info | Hang on, you said "out" on the BoxIn node? Don’t you mean "in"? | {{ info | Hang on, you said "out" on the BoxIn node? Don’t you mean "in"? | ||
Line 124: | Line 128: | ||
== Useful links == | == Useful links == | ||
[http://www.gafferhq.org/documentation/ | [http://www.gafferhq.org/documentation/{{latestGafferVersion}}/WorkingWithTheNodeGraph/BoxNode/index.html# Box node introduction] | ||
[http://www.gafferhq.org/documentation/ | [http://www.gafferhq.org/documentation/{{latestGafferVersion}}/WorkingWithThePythonScriptingAPI/TutorialNodeGraphEditingInPython/index.html Working with the Node Graph scripting tutorial] | ||
[https://github.com/GafferHQ/gaffer/blob/master/include/Gaffer/BoxIO.h The BoxIO module] | [https://github.com/GafferHQ/gaffer/blob/master/include/Gaffer/BoxIO.h The BoxIO module] |
Revision as of 21:07, 21 July 2024
Gaffer’s Box node is a staple building block sharing chunks of node graph (via the Reference system), or quickly building tools via expressions and promoted plugs.
The Gaffer docs have a page covering Box nodes and how to use them. In this guide we take a quick look at how to re-create a simple GroundPlane tool made in the UI, using only Gaffer’s python API.
This guide assumes you are familiar with working with Box nodes in the UI, including promoting plugs and how in/out nodes work. You also need to be comfortable with using Gaffer’s Python API to work with the node graph.
Introduction
For those reading who have used the Plane node. Its ‘z-up’ orientation is less than helpful if you just want a bit of ground for some reason or another. It is also tiny.
Here we’re going to make a simple box that encapsulates the network shown to the left so that you can add a plane with the right orientation to your scene. Exposing a few useful plugs to make it easy to tweak.
The script
All of the code below can be run directly in the Python Editor. Let start first by importing a few pre-requisites we’ll need later:
import Gaffer
import GafferScene
import imath
Making the Box
The box node, as with all Gaffer nodes, is made by literally constructing an instance of the Box class and adding it to the graph. In this script, we’ll just put it at the root. In a real tool, you’d probably wan’t to use GafferUI.EditMenu.scope() to get the root visible in the user’s Graph Editor.
box = Gaffer.Box( "GroundPlane" )
root.addChild( box )
As this is a script and we don’t know what nodes may already be in the graph, when creating new nodes, we work with the node instance directly rather than adding it to it’s parent and using the parent["NodeName"] syntax. The latter works in simple cases, but if there is already a node with that name, the new one will be renamed, so the sub-script syntax would give the existing node, not your new one!
The internal network
We need a Plane, to set some non-default values so it’s more useful, freeze it’s transform and use a Parent node to insert it into the incoming scene. Make these nodes, connect them up and add them to the Box:
plane = GafferScene.Plane( "Plane" )
plane["transform"]["rotate"].setValue( imath.V3f( -90, 0, 0 ) )
plane["dimensions"].setValue( imath.V2f( 100, 100 ) )
plane["name"].setValue( "ground" )
freeze = GafferScene.FreezeTransform( "FreezeTransform" )
# Note, don't confuse 'parent' here with the built-in variable
# available in Python expressions. This is just a local holding
# our 'Parent' node (I should have used a less confusing example!)
parent = GafferScene.Parent( "Parent" )
parent["parent"].setValue("/")
freeze["in"].setInput( plane["out"] )
parent["child"].setInput( freeze["out"] )
box.addChild( plane )
box.addChild( freeze )
box.addChild( parent )
Promoting plugs
We now have our network inside the box, but the box has no input or output. We can use the Gaffer.BoxIO node’s static promote method to promote the in/out plugs on the Parent node, as a user might do using the right-click menus in the UI:
Gaffer.BoxIO.promote( parent["in"] )
Gaffer.BoxIO.promote( parent["out"] )
You can use exactly the same function to promote plugs that appear in the Node Editor instead of in the Graph Editor (they’re all just plugs after all). BoxIO.promote preserves the plugs existing metadata, which is what controls where they’re presented, so it knows whether they should be on the top or bottom or sides of the box.
Gaffer.BoxIO.promote( plane["name"] )
Gaffer.BoxIO.promote( parent["parent"] )
Gaffer.BoxIO.promote( plane["divisions"] )
Gaffer.BoxIO.promote( plane["dimensions"] )
The promote() method takes care of creating, connecting and configuring BoxIn/ BoxOut nodes and plugs on the Box itself. You don’t need to manually manage these.
You should now have a functional Box just like the one made in the UI.
Disabling the node (a.k.a finding inputs and outputs).
In order to allow a user to disable/bypass the Box, we need to provide a pass-through connection (explained here) that defines what to output when the Box is disabled. To do this, we’ll need to get the BoxIn and BoxOut nodes so we can connect the boxOutNode["passThrough"] plug to the boxInNode["out"] plug.
Hang on, you said "out" on the BoxIn node? Don’t you mean "in"?
To understand this, we’ll have to look at a few details of how Box nodes work. A box node has in/out plugs. When you look at the box node from the outside you are looking at plugs on the box node itself. But you also want to see them when you’re looking at the graph inside the box so you can hook things up.
As its Gaffer, and we try to keep the fundamentals simple and consistent. Rather than invent something new and esoteric, the BoxIO code you used earlier creates child nodes that are either BoxIn or BoxOut to represent the box’s input/output plugs when viewing the graph inside the box in the Graph Editor and connects these from/to the box’s actual inputs and outputs so data can flow from outside, to inside and back again, ie:
As they’re __ prefixed (ie: private plugs), you don’t see these ‘bridging’ plugs and connections in the UI (it’d just be confusing too).
The name of these internal nodes also determines the name of the plug on the outside of the box – which works well as these plug names need to be unique.
So, you need the out plug of the BoxIn node as thats the plug that’s visible when looking at the inside of the box, that carries the data from the in plug on the outside of the box.
Back, to connecting up the passthrough. There are a couple of ways to find the plugs to do this.
Getting the I/O nodes for an existing box
In this simple case, the Box node in plug is connected to its child BoxIn node, and it’s out plug is connected from it’s child BoxOut node. A such, you can use the following:
boxInNode = box["in"].outputs()[0].node()
boxOutNode = box["out"].getInput().node()
boxOutNode["passThrough"].setInput( boxInNode["out"] )
A safer route
The above works, but relies on knowing the correct names for the input/output plugs on the box. If you’ve promoted several inputs and outputs, they have to have unique names so they might not be what you think.
When you promote a plug using Gaffer.BoxIO.promote() it returns the corresponding plug on the outside of the box. So we could amend our promotion code to the following:
boxInPlug = Gaffer.BoxIO.promote( parent["in"] )
boxOutPlug = Gaffer.BoxIO.promote( parent["out"] )
# Add a passthrough
boxOutNode = boxOutPlug.getInput().node()
boxInNode = boxInPlug.outputs()[0].node()
boxOutNode["passThrough"].setInput( boxInNode["out"] )
You could also go backwards, from the parent node:
boxOutNode = parent["out"].outputs()[0].node()
boxInNode = parent["in"].getInput().node()
Box Icons
By default, Box nodes are drawn with a ‘box’ icon. When you change this in the UI Editor, it simply edits the node’s metadata. You can do the same yourself if you want to remove (or change) the icon:
# remove
Gaffer.Metadata.registerValue( box, 'icon', None )
# change
Gaffer.Metadata.registerValue( box, 'icon', 'grid.png' )
NOTE: Images specified by a relative path need to be on $GAFFER_IMAGE_PATHS.
Useful links
Working with the Node Graph scripting tutorial
Finding the ‘current’ graph root with GafferUI.EditMenu.scope()
The final script
import Gaffer
import GafferScene
import imath
# The box
box = Gaffer.Box( "GroundPlane" )
Gaffer.Metadata.registerValue( box, 'icon', None )
root.addChild( box )
# Internal network
plane = GafferScene.Plane( "Plane" )
plane["transform"]["rotate"].setValue( imath.V3f( -90, 0, 0 ) )
plane["dimensions"].setValue( imath.V2f( 100, 100 ) )
plane["name"].setValue( "ground" )
freeze = GafferScene.FreezeTransform( "FreezeTransform" )
parent = GafferScene.Parent( "Parent" )
parent["parent"].setValue("/")
freeze["in"].setInput( plane["out"] )
parent["child"].setInput( freeze["out"] )
box.addChild( plane )
box.addChild( freeze )
box.addChild( parent )
# Promote i/o
boxInPlug = Gaffer.BoxIO.promote( parent["in"] )
boxOutPlug = Gaffer.BoxIO.promote( parent["out"] )
# Promote useful Node Editor plugs
Gaffer.BoxIO.promote( plane["name"] )
Gaffer.BoxIO.promote( parent["parent"] )
Gaffer.BoxIO.promote( plane["divisions"] )
Gaffer.BoxIO.promote( plane["dimensions"] )
# Add a passthrough
boxOutNode = boxOutPlug.getInput().node()
boxInNode = boxInPlug.outputs()[0].node()
boxOutNode["passThrough"].setInput( boxInNode["out"] )