Guide to Writing Fuses for Resolve/Fusion Part 2

Lifecycle

Oh but if that was just the story that’d be great. We also need to make our fuse do something! And to do that we need to know how and when fusion calls it. i.e. we need to know the lifecycle.

We know already that when fusion starts it loads our lua file and it runs it. That’s how we can call the FuRegisterClass function to tell it about our tool. We could do other things there if there are things we want to do when fusion starts, but generally we don’t. We wait for fusion to call our script in one of several functions.

We’ll have to go into detail about these calls, but for this section we’ll start by just understanding what they are, what they’re for, and when they occur.

Create – if this function is in your script, it will be called when your tool is added to the composition. There’s no arguments. It’s just a kind of “hello, your tool is being setup do you want to do anything?”. Generally we do – we want to set up some inputs and some controls in the inspector so that the tool has information to work from and people can do stuff with it! But more detail about that later. For now it’s enough to know this is a very important function in pretty much every fuse.

OnAddToFlow – related to OnCreate is OnAddToFlow . This generally isn’t used. But by the spec it fires after Create and before all NotifyChanged and OnConnected calls. You can therefore use it, for example, to change values on Inputs you’ve made in Create and be sure they exist.

NotifyChanged – if our tool is set up, the next thing that can fire is NotifyChanged. Here’s the spec: NotifyChanged(object input, object parameter, number time) . This gets called whenever an input (such as a control in the inspector) that is set to notify changes, changes value. This gives us the option to have the tool respond to those changes as they happen. For example: if somebody clicks a button.

OnConnected – By definition this is: OnConnected(link, old, new) . This fires when an input gets a connection in the flow or any upstream tool does or when one is disconnected

CheckRequest – passes req – an object representing the current request. It’s called once or more before Process and can be used for things like silently switching out values the user has set in a specific instance without changing the interface. Generally it isn’t used.

PreCalcProcess – passes req – an object representing the current request. Here the return is supposed to tell fusion about the image size, DoD, etc. Basically we pass a sample of the output back. If not present then fusion will try to get this information by calling the Process function.

Process – passes req – an object representing the current request. This is where the bulk of the frame based work is done. It’s called at least once per frame (unless fusion has cached something). If there’s no PreCalcProcess function then beware that it can be called multiple times on some frames. There’s no obligation to have all of these functions, in general your average fuse will contain Create, NotifyChanged, and Process.

The Create Function

The Create function is the first function called by Fusion when a node using the script comes into existence. It’s mostly used to set up parameters/controls in the inspector (the sliders, color choices, etc)

To add a parameter/control we call self:AddInput and we assign the result to a variable so we can get it’s value later.

We can add a new page to the inspector with self:AddControlPage(“PageName”), where page name is a string with the name you want to call the page. Any AddInput calls after this are added to the last page created, so order is important.

We can add a section to a page with self:BeginControlNest(string controlname, string scriptname, boolean is_expanded, table taglist). Here controlname is the label, scriptname is a machine name (alphabetical characters only, no spaces), is_expanded is true to expand and false to contract, taglist is unknown so always use {} ! Controls listed after the BeginControlNest statement are put in the nest until the line self:EndControlNest() is reached. Which unsurprisingly ends the control nest!

Let’s write out our Create function and then talk about what it does:

function Create()
 -- Called everytime a node is created (due to the user adding it, reload, or loading from file)
 -- Often used to create user interface elements. 
 InGain = self:AddInput("Gain", "Gain", {
  LINKID_DataType = "Number",
  INPID_InputControl = "SliderControl",
  INP_Integer = false,
  INP_Default = 0,
  INP_MinScale = -1,
  INP_MaxScale = 1,
 })
 self:BeginControlNest("Colour Channels", "ColourChannels", true, {})
  InRed = self:AddInput("Red Gain", "RedGain", {
   LINKID_DataType = "Number",
   INPID_InputControl = "SliderControl",
   INP_Integer = false,
   INP_Default = 0,
   INP_MinScale = -1,
   INP_MaxScale = 1,
  }) 
  InGreen = self:AddInput("Green Gain", "GreenGain", {
   LINKID_DataType = "Number",
   INPID_InputControl = "SliderControl",
   INP_Integer = false,
   INP_Default = 0,
   INP_MinScale = -1,
   INP_MaxScale = 1,
  }) 
  InBlue = self:AddInput("Blue Gain", "BlueGain", {
   LINKID_DataType = "Number",
   INPID_InputControl = "SliderControl",
   INP_Integer = false,
   INP_Default = 0,
   INP_MinScale = -1,
   INP_MaxScale = 1,
  })   
 self:EndControlNest()
end

Firstly we call self:AddInput and assign the result to a variable called InGain. As we discussed, this allows us to create a control in the inspector and later get its value. The first parameter is the name that shows to the end user, the second is the machine name (it’s used for example when using this control in expressions). In the first instance I’ve set them both to “Gain”, which is easy! The third parameter is a table/array of properties.

The first of these is LINKID_DataType – this can be one of: Gradient, Image, Number, Particles, Point, Text . Generally you’ll use Number, Point, and Text.

Which one you use is related to the second property: INPID_InputControl. This property specifies the type of control (in this case a SliderControl), which will obviously influence the data type!

Finally there’s a variety of additional properies that vary by Control Type. In this case we say it’s not an Integer (meaning it’s a float!), that the default value is 0 and that this slider can go from a minimum of -1 to +1.

This is enough code to put a slider control on the screen. But for the example we want more and we want to do something fancy like group them in a nest.

So the code has the BeginControlNest and EndControlNest lines that I explained earlier around 3 more copies of pretty much the same code. Each of those sliders just has a different name and a different variable so that we can reference them later.

The Controls

So let’s talk controls, because I’ve only shown you had to make one type – the Slider Control. Although that, in my opinion, is arguably the most used – you might want to make some more. I’d like to make some sort of Inspector UI builder to make this easier, but for now I’m just going to list some of the code examples from some old documentation and comment them up a bit more for you.

ButtonControl

Adds a button

InLabel = self:AddInput("This is a Button", "Label1", {
		LINKID_DataType = "Text",
		INPID_InputControl = "ButtonControl",
		INP_DoNotifyChanged = true, // Calls NotifyChanged when clicked
		INP_External = false, // Cannot be animated
	})

CheckboxControl

Actually, another of my favourites. Adds a tick box.

InR = self:AddInput("Red", "Red", {
	LINKID_DataType = "Number", // Checkboxes return 0 for false, 1 for true
	INPID_InputControl = "CheckboxControl",
	INP_Integer = true, // Always going to be true!
	INP_Default = 1.0, // Default on (1). For off it would be 0
	ICD_Width = 0.25, // Width it takes in inspector (1 would be 100%)
})	

ColorControl

Adds a colour selector box. It’s a very convenient way of picking colours. Note here that the 4 AddInput statements work together to make the single colour picker in the interface. So you need all 4. IC_ControlGroup is the same for each one, so Fusion knows to group them together as one control and IC_ControlID is unique for each element:

	InR = self:AddInput("Red", "Red", {
		LINKID_DataType = "Number",
		INPID_InputControl = "ColorControl",
		INP_MinScale = 0.0,
		INP_MaxScale = 1.0,
		INP_Default  = 1.0,
		ICS_Name = "Color",
		IC_ControlGroup = 1, // Note the Control Group Here is the same each time
		IC_ControlID = 0, // Note the Control ID Here is different each time
		})
		
	InG = self:AddInput("Green", "Green", {
		LINKID_DataType = "Number",
		INPID_InputControl = "ColorControl",
		INP_MinScale = 0.0,
		INP_MaxScale = 1.0,
		INP_Default  = 1.0,
		IC_ControlGroup = 1,
		IC_ControlID = 1,
		})
		
	InB = self:AddInput("Blue", "Blue", {
		LINKID_DataType = "Number",
		INPID_InputControl = "ColorControl",
		INP_MinScale = 0.0,
		INP_MaxScale = 1.0,
		INP_Default  = 1.0,
		IC_ControlGroup = 1,
		IC_ControlID = 2,
		})
		
	InA = self:AddInput("Alpha", "Alpha", {
		LINKID_DataType = "Number",
		INPID_InputControl = "ColorControl",
		INP_MinScale = 0.0,
		INP_MaxScale = 1.0,
		INP_Default  = 1.0,
		IC_ControlGroup = 1,
		IC_ControlID = 3,
		})

ComboControl

A ComboControl is basically a dropdown list. This is useful to give the user a variety of options to select from.

	InOperation = self:AddInput("Operator", "Operator", {
		LINKID_DataType = "Number",
		INPID_InputControl = "ComboControl",
		INP_Default = 0.0,
		INP_Integer = true,
		ICD_Width = 0.5,
		{ CCS_AddString = "Over", }, // The text is quotes in each of these lines is the option name shown to the user
		{ CCS_AddString = "In", },
		{ CCS_AddString = "Held Out", },
		{ CCS_AddString = "Atop", },
		{ CCS_AddString = "XOr", },
		CC_LabelPosition = "Vertical", // Other possibility is Horizontal
		})

FileControl

Allows file selection

	InFile = self:AddInput("File", "File", {
		LINKID_DataType = "Text",
		INPID_InputControl =  "FileControl",
		FC_ClipBrowse = true, // Specifies we are looking for images. Set to false for other file types
		})

LabelControl

Probably the easiest. This isn’t really a control as such, it’s just a label.

	InLabel = self:AddInput("This is a Label", "Label1", {
		LINKID_DataType = "Text",
		INPID_InputControl = "LabelControl",
		INP_External = false, // Not animatable
		INP_Passive = true, // Tells fusion that this control has no effect on the rendered result
	})

OffsetControl

The OffsetControl allows the user to enter an offset – basically a point

	InCenter = self:AddInput("Center", "Center", {
		LINKID_DataType = "Point",
		INPID_InputControl = "OffsetControl",
		INPID_PreviewControl = "CrosshairControl", // The type of visualisation in the viewer
		})

RangeControl

The RangeControl, like the ColorControl is another case where it’s actually two things that are presented to the end user as one control. It let’s them select a high low range. Note again the use of IC_ControlGroup and IC_ControlID

	InGLow = self:AddInput("Green Low", "GreenLow", {
		LINKID_DataType = "Number",
		INPID_InputControl = "RangeControl",
		INP_Default = 0.0,
		IC_ControlGroup = 2,
		IC_ControlID = 0,
		})			
	
	InGHigh = self:AddInput("Green High", "GreenHigh", {
		LINKID_DataType = "Number",
		INPID_InputControl = "RangeControl",
		INP_Default = 1.0,
		IC_ControlGroup = 2,
		IC_ControlID = 1,
		})

SliderControl

The slider control we have already seen, so you’ll have to scroll back up for the examples!

Ready for Part 3?

What we don’t touch in that information, of course, is how to read them. And that’s something for Part3. Part3 is coming soon!

Leave a Reply