Useful Scripts/Snippets

From VFXPedia

< Useful Scripts
Revision as of 07:26, 22 June 2012 by Tilt (Talk | contribs)
(diff) ← Older revision | Current revision (diff) | Newer revision → (diff)
Jump to: navigation, search

This page is a place to share small bits of code (LUA and Python) that solve a specific task. A lot of example code can be found by digging through the scripts that are available, but often the variables they rely on are scattered throughout the code. The snippets on this page, however, should be self-contained so they can be pasted into your own scripts easily.


Contents

Getting and Setting Inputs

Two sections in the scripting docs are already dealing with inputs and how they are animated. To recap:


bc = BrightnessContrast()
 
-- won't work, as this assigns the Gain slider's Input object to the variable (seen by typing ==x at the console)
x = bc.Gain
-- get value of a property that isn't animated
x = bc.Gain[TIME_UNDEFINED]
-- if a property is animated, you need to query it at the current time:
x = bc.Gain[comp.CurrentTime]
-- ...or any other time you want
x = bc.Gain[20]
 
-- set input's value. If tool is animated, a keyframe will be created at the current time.
bc.Gain[comp.CurrentTime] = 0.5
-- shorthand to set input's value if input is not animated. If it is, nothing will happen.
bc.Gain = 0.5

The shorthand doesn't work, however, if the input is no longer accessed via the dot-notation of its parent object. It's a LUA pitfall or rather a side effect of how this shorthand notation is implemented. Consider this:

-- this doesn't work. It will assign "2" to the inp variable instead of changing the input object.
inp = bc.Gain
inp = 2
 
-- using square brackets you can make it work:
inp = bc.Gain
inp[TIME_UNDEFINED] = 2       -- works if input isn't animated
inp[comp.CurrentTime] = 2     -- works always 

This, however, isn't enough if you want to animate an input. Usually, you just assign it a BezierSpline (1D), Path or XYPath (2D) object or whatever modifier you require:

-- animate gain slider:
bc.Gain = BezierSpline()
-- remove animation:
bc.Gain = nil
 
-- If you use a temporary variable, this won't work. It will assign an empty BezierSpline to the temporary variable
-- instead of connecting it to the gain slider:
inp = bc.Gain
inp = BezierSpline()
 
-- but even the array notation doesn't work in this case.
inp = bc.Gain
inp[TIME_UNDEFINED] = BezierSpline()
 
-- the correct way to animate an input in this case is via the ConnectTo() method:
inp = bc.Gain
inp:ConnectTo(BezierSpline())
 
-- but you could also use this option if you still know the tool the input belongs to ("bc" in this case):
bc[inp.ID] = BezierSpline()
-- ...which uses the input's scripting ID as if you had written this:
bc["Gain"] = BezierSpline()


Following Inputs and Outputs

If you want to follow the chain of tools in your flow, you need to know some handy commands that deal with image inputs and outputs. (note: sliders and buttons on a tool are also called Inputs. Their values, however, are numbers or strings.)

A tool's image input can be accessed like any other input using the dot notation:

inp = tool.Background

However, the name is not always "Background", some tools have a main input called "Input". So you need to use the method FindMainInput(). An input is connected to another tool's output, and the output will tell you which tool it belongs to. Here's the complete helper function to traverse backwards through the flow:

function GetUpstream(tool)
   -- the 1 means 1st input in case there are more (merges, dissolves)
   local inp = tool:FindMainInput(1)
   -- get output connected to the input
   local outp = inp and inp:GetConnectedOutput() or nil
   -- finally get the upstream tool or nil if no tool is connected
   return ( outp and outp:GetTool() or nil )
end

Similarly, this function returns the downstream tool(s) as an array:

function GetDownstream(tool)
   local outp = tool:FindMainOutput(1)
   local inpTable = outp:GetConnectedInputs()
   local tools = {}
   for i,t in ipairs(inpTable) do
      tools[i] = t:GetTool()
   end
   return tools
end

Creating a Loader or Saver

When creating a Loader or Saver, the file dialog will pop up. To prevent this, lock the composition first. But don't forget to unlock the comp after your work is done.


composition:Lock()
local new_loader = Loader({Clip = "C:\\mymovie.avi"})
composition:Unlock()

or if you want to use the AddTool method, which allows you to define the coordinates the new tool will be placed at:

composition:Lock()
local new_loader = composition:AddTool("Loader", 100, 100)
new_loader.Clip[TIME_UNDEFINED] = "C:\\mymovie.avi"
composition:Unlock()

Remember that the script might abort unintentionally due to an error or intentionally by using return or exit() somewhere along the way. So unlock the comp as soon as possible and catch any errors that might occur.

There's a downside to using Lock() and Unlock(). These commands will clear caches and force a re-render of the current comp. To disable the file selector while creating Loaders it might be wiser to temporarily turn off the corresponding option in the preferences. This can be done in a script as well. The following snippet (hat tip to User:Stuart) will first save and later restore the original value of the AutoClipBrowse preference:

autobrowse = fusion:GetPrefs("Global.UserInterface.AutoClipBrowse")
fusion:SetPrefs("Global.UserInterface.AutoClipBrowse", false) 
-- Add loader
fusion:SetPrefs("Global.UserInterface.AutoClipBrowse", autobrowse)

Creating a Saver With Custom Options

This snippet shows how to define the format options of a saver. A dialog box is displayed that allows for selecting one of several predefined formats.
-- define some output formats
OutputFormats = {
	{Label = "EXR float", Ext = ".exr", Settings = {["OpenEXRFormat.Depth"] = 1}},
	{Label = "DPX log", Ext = ".dpx", Settings = {["DPXFormat.BypassConversion"] = 0}},
	{Label = "DPX linear 10bit", Ext = ".dpx", Settings = {["DPXFormat.BypassConversion"] = 1}},
	{Label = "JPG 8bit 85% Quality", Ext = ".jpg", Settings = {["JpegFormat.Quality"] = 85}},
}
 
-- create list of format names for dialog box
formatItems = {}
for i,f in pairs(OutputFormats) do
	table.insert(formatItems, f.Label)
end
ask = composition:AskUser("Create Saver", {
	{"f", Name = "Format: ", "Dropdown", Options = formatItems},
	})
if ask == nil then return end
 
-- create saver (default filename based on composition name)
composition:Lock()
clipname = "Comp:" .. composition:GetAttrs().COMPS_Name .. ".0000" .. OutputFormats[ask.f + 1].Ext 
sv = Saver({Clip = clipname})
-- loop through table of pre-defined formats and transfer settings to saver
for key,val in pairs(OutputFormats[ask.f + 1].Settings) do
	sv[key] = val
end
composition:Unlock()


Updating a Loader

Updating an existing Loader with a new file name can lead to unexpected behavior, especially if the new clip's frame range differs from the old one. There is a simple solution that solves all of this: Simply refresh the Loader by toggling pass-through on and off.
-- "ld" is a Loader object
ld.Clip[TIME_UNDEFINED] = ""
ld.Clip[TIME_UNDEFINED] = "Comp:new_clip.0000.exr"
-- optionally reset trimming and "hold first frame":
ld.HoldFirstFrame = 0
ld.ClipTimeStart = 0
-- now toggle pass-through to refresh the loader
ld:SetAttrs({TOOLB_PassThrough = true})
ld:SetAttrs({TOOLB_PassThrough = false})

If you want to trigger "auto detect clip length" for a loader, you can also use this command (mentioned by Eric Doiron on the mailing list):

-- "ldr" is a Loader object
ldr.Clip[comp.CurrentTime] = ldr.Clip[comp.CurrentTime]

Changing a Node's Color

A tool's color can be changed by setting its TileColor and TextColor properties. They each require a LUA table that has keys for R, G and B. Its values are floating point from 0 to 1. The following snippet demonstrates how to loop through all bitmap masks and color them according to the channel they're set to.
-- get all bitmap masks in comp
masks = comp:GetToolList(false, "BitmapMask")
 
-- Predefined colors in an array whose keys match the channel selection dropdown of bitmap masks.
colors = {
	Red = {R = 1, G = 0, B = 0},
	Green = {R = 0, G = .8, B = 0},
	Blue = {R = 0, G = 0, B = 1}}
 
for i,m in pairs(masks) do
	-- for values without a corresponding key in the colors array, "nil" will be assigned
	-- to TileColor, which resets the mask's color to its default hue.
	m.TileColor = colors[ m.Channel[TIME_UNDEFINED] ]
end

This could even be put into an event suite to be run each time the mask is selected or the comp is opened.


Switching to the Script Console

If you want the user to notice an error message you have printed to the console, you can also switch the view via scripting:


if filename == "" then
   print("Filename is missing!")
   composition:GetFrameList()[1]:SwitchMainView('ConsoleView') 
   exit()
end


How can I find out all abbreviations for Fusion's tools?

Type this into the Lua console:


for i,v in ipairs(fu:GetRegSummary(CT_Tool)) do if v.REGS_OpIconString then print(v.REGS_OpIconString .." = ".. v.REGS_Name) end end

(suggested by Chad Capeland on the pigsfly forum)


How can I print a list of all inputs and outputs that a tool has?

Type this into the Lua console to print a list of all input IDs as well as their names as printed in the GUI.


for _, inp in ipairs(comp.ActiveTool:GetInputList()) do print(inp:GetAttrs().INPS_ID .. ": " .. inp:GetAttrs().INPS_Name) end

Use this to print all outputs (most tools only have one, but the Tracker for example has a lot more).

for _, out in ipairs(comp.ActiveTool:GetOutputList()) do print(out:GetAttrs().OUTS_ID .. ": " .. out:GetAttrs().OUTS_Name) end


How can I find out valid values for combo controls (dropdown lists)?

If you want to assign values to a combo control input, there might be a difference between the string that is displayed in the dropdown list and the string that is expected by Fusion when it comes to scripting. This concept is called FuID and allows the interface strings to be user-friendly descriptions that can even differ from one Fusion release to the next, while making sure that scripts and compositions created for older versions of Fusion will continue to work. To find out the FuIDs you need for scripting, just print (Python) or dump (LUA) the attributes of a tool's input. The info is contained in the INPIDT_ComboControl_ID table:
-- color space dropdown in the Gamut tool
gam1 = GamutConvert()
dump(gam1.SourceSpace:GetAttrs()['INPIDT_ComboControl_ID'])
-- format dropdown in the FBX Exporter
geo_out = ExporterFBX()
dump(geo_out.Format:GetAttrs()['INPIDT_ComboControl_ID'])

(credits to Ilia Zaslavsky on the Fusion mailing list)

For loaders, an attribute table like this also contains the channel names of EXR files. The first FuID really is that funky (it's the "None" entry).

-- make sure loader is really loading an EXR file...
local tattrs = tool:GetAttrs()
if tattrs.TOOLS_RegID == "Loader" and tattrs.TOOLST_Clip_FormatName[1] == "OpenEXRFormat" then
   dump(tool.Clip1.OpenEXRFormat.RedName:GetAttrs().INPIDT_ComboControl_ID)
end
 
-- 1 = SomethingThatWontMatchHopefully
-- 2 = R
-- 3 = G
-- 4 = B
-- 5 = A
-- 6 = Z
-- 7 = GI.B
-- 8 = GI.G
-- 9 = GI.R
-- ... 

(credits to Sven Neve)


Using FuIDs in InTool scripts

You can assign strings to inputs that expect a FuID type. For example, this works in comp and tool scripts:
Renderer3D1.Camera = "Camera3D1"

InTool scripts (like the Frame Render Script), however, don't support this shortcut. In this case, you need to turn your string into a FuID object. This snippet demonstrates a frame render script for a Renderer3D tool, that loops through (pre-defined) cameras automatically, depending on the current frame number:

-- these 4 cameras have to exist in your scene
cams = {"Camera_FRONT", "Camera_LEFT", "Camera_RIGHT", "Camera_BACK"}
Camera = FuID(cams[time % 3 + 1])


How can I scroll the flow view to a specific tool?

This can be done by calling the SetActiveTool() method:


-- scroll to first Loader
tlist = composition:GetToolList(false, "Loader")
if #tlist > 0 then
    composition:SetActiveTool(tlist[1])
end


Three ways of printing the current frame in a TextTool

This demonstrate how to print the current framenumber in a TextTool by either using modifiers, FrameRenderScript or SimpleExpressions.

Text1 is the name of the texttool in this example.
TimeCode Modifier:

Text1.StyledText = TimeCode({Hrs = 0, Mins = 0, Secs = 0, Frms = 1})

FrameRenderScript:

Text1.FrameRenderScript = "StyledText = time"

Simple Expression:

Text1.StyledText:SetExpression("Text(time)")


Add a macro from within a script

This way you can add a macro from within a script.


s = "C:\\macro.setting"
f = eyeon.readfile(s)
comp:Paste(f)

(credits to John Barclay)


Parsing Paths

To extract elements from a string (for example directory names), use string.match which is able to return multiple variables in one call:
path = "x:\\projects\\cool_show\\shots\\ABC_000_010\\comps\\"
project, shot = path:lower():match("\\projects\\([^\\]+)\\shots\\([^\\]+)\\")
-- result: "cool_show", "abc_000_010" 

The string is lowercased so it also works if the string contains capital letters as in Comps or Projects. This will also return a lower-cased shot name. If you want to preserve case, you'll find that LUA misses a regular expression flag that does case-insensitive matching. You can use this workaround (only practical for short strings):

project, shot = path:match("\\[Pp][Rr][Oo][Jj][Ee][Cc][Tt][Ss]\\([^\\]+)\\[Ss][Hh][Oo][Tt][Ss]\\([^\\]+)\\")
-- result: "cool_show", "ABC_000_010" 

This is an example that increases a version number inside a string:

path = "x:\\COOL_SHOW\\render\\shot01_V009"
-- look for version number (in real situations it might be necessary to improve the regexp depending
-- on your naming conventions, since this will also catch "v6motorcommercial")
oldversion = path:match("[Vv](%d+)")
if oldversion ~= nil then
   -- increase version number and produce a string with the same padding as before (e.g. 010)
   newversion = string.format("%0" .. #oldversion .. "d", oldversion + 1)
   -- this works but would lowercase the V character all the time
   new_path = path:gsub("[Vv]" .. oldversion, "v" .. newversion)
   -- better: captures the V-character (parentheses) and uses it again as a back-reference (%1) so its case is preserved
   new_path = path:gsub("([Vv])" .. oldversion, "%1" .. newversion)
end

For more string tips, refer to the scripting documentation.


Saving AskUser Dialog Values

If you present a dialog to the user via comp:AskUser(), it's a good idea to save the values. This way, the user doesn't have to re-enter everything when the script is executed multiple times.

Fusion allows you to save arbitrary data to LUA's "globals" table. These values will be kept in memory as long as Fusion is running. Here's a bare-bones script that demonstrates how to use globals as a data storage:

-- retrieve options table from globals (if it exists)
-- use a name that doesn't clash with other scripts, "MySavedOptions" is just an example
options = globals.MySavedOptions or {}
 
-- build user dialog. Slider will be set to saved value or 0 if no saved value was found
dialog = {}
dialog[1] = {"value", "Slider", Name = "Select Value:", Default = options.value or 0}
 
ret = composition:AskUser("Save Settings Demo:", dialog)
if ret == nil then
	exit()
end
 
-- remember options for next invocation of script
globals.MySavedOptions = ret