Eyeon:Script/Tutorials/Slate Manipulation/Tool Scripts and Creating New Comps

From VFXPedia

Jump to: navigation, search

Lesson 3.3 : Tool Scripts and Creating New Comps

One of the fundamental issues with having a slate in a network location of some sort is that it's available for users to manipulate in the way that users frequently do. As such, a slate that might have been painstakingly put together could get overwritten and no longer interact properly with the script you wrote.

There are ways to solve this problem.

One is to set the file to read only. Another is to write some sort of program to overwrite the existing comp every once in a while. Neither of those options will always work.

Another solution would be to alter the script to dynamically create the slate composition.

To illustrate a new scripting method, we'll also use this opportunity to utilize "tool" scripts instead of "composition" scripts - the type we've been using up to this point.

As highlighted in the beginning of this manual, scripts can also be stored in the "tool" subfolder of the scripts directory. Save a new script in your \Fusion 5\Scripts\Tool\ directory called "Create Slate from Saver.eyeonScript".

To run the script, right click on a tool, go to its scripts menu, and then select the desired script item. When this is run a variable with the tool's object UserData is passed into a variable called, fittingly, "tool".

Let's test that out with a script that prints the name of the tool to the console:

print(tool:GetAttrs().TOOLS_Name)

Now try running the script. The name of the tool should show up in the console.

This can be useful in the case of our previous script, because it allows us to bypass the initial step of searching through the composition for the appropriate saver to utilize as the output location for the slate. Delete the above code. The first step is to see if the tool the user just tried to run the script on is a saver.

if tool:GetID() ~= "Saver" then
        print("ERROR: This is not a saver.")
end

Instead of using the COMPS_Name attribute to derive the name of the shot, how about trying to use the saver's Clip instead. As a test, let's assume that the saver's output location was something like this:

c:\Renders\Example_Project\Shot01\final\comp\Shot01_v101.0000.tga

We can use one of the functions included in the eyeon.eyeonscriptlib to derive its name - eyeon.parseFilename(). eyeon.parseFilename was written for the express purpose of disassembling filenames into a set of coherent pieces for simple scripting use.

Try seeing what the function returns in the console for the above file location.

==eyeon.parseFilename("c:\\Renders\\Example_Project\\Shot01\\final\\comp\\Shot01_v101.0000.tga")
table: 022F1E98
        Number = 0
        Path = c:\Renders\Example_Project\Shot01\final\comp\
        Extension = .tga
        SNum = 0000
        Name = Shot01_v101.0000
        CleanName = Shot01_v101.
        UNC = false
        Padding = 4
        FullPath = c:\Renders\Example_Project\Shot01\final\comp\Shot01_v101.0000.tga
        FullName = Shot01_v101.0000.tga

For a description of each of those categories, we can turn to the eyeon.eyeonscriptlib:

------------------------------------------------------------------------------
-- parseFilename()
--
-- this is a great function for ripping a filepath into little bits
-- returns a table with the following
-- 
-- FullPath    : The raw, original path sent to the function
-- Path               : The path, without filename
-- FullName    : The name of the clip w\ extension
-- Name     : The name without extension
-- CleanName: The name of the clip, without extension or sequence
-- SNum               : The original sequence string, or "" if no sequence
-- Number      : The sequence as a numeric value, or nil if no sequence
-- Extension: The raw extension of the clip
-- Padding     : Amount of padding in the sequence, or nil if no sequence
-- UNC         : A true or false value indicating whether the path is a UNC path or not
------------------------------------------------------------------------------ 

The field that will be of the most use for this lesson would be the CleanName field. As is seen in the returned table, the CleanName field does not take into account the appearance of separator marks like ".", "_" or "-" before the frame number. The script will need to test if the last character is one of these separator characters.

fileParse = {}
 
fileParse = eyeon.parseFilename("c:\\Renders\\Example_Project\\Shot01\\final\\comp\\Shot01_v101.0000.tga")
lastChar = string.sub(fileParse.CleanName, string.len(fileParse.CleanName), string.len(fileParse.CleanName))
 
if lastChar == "." or lastChar == "_" or lastChar == "-" then
       -- do something
end

If the last character is one of these, it should be removed.

if lastChar == "." or lastChar == "_" or lastChar == "-" then
        newCleanName = string.sub(fileParse.CleanName, 1, string.len(fileParse.CleanName) - 1)
else
        newCleanName = fileParse.CleanName
end

The next step is to pop up the dialog from the previous lesson to see if we've accurately guessed what the settings for the slate should be.

The slate location should be removed, and the default for the Shot Name should be changed to the newCleanName variable.

ret = AskUser("Slate Options", {{"Project Name", "Text", Default = "Example Project", Lines = 1},
                     {"Shot Name", "Text", Default = newCleanName, Lines = 1 },
                     {"Date", "Text", Default = dateDefault, Lines = 1},
                     {"Artist", "Text", Default = usernameDefault, Lines =1},
                     {"Frame Range", "Text", Default = framerangeDefault, Lines =1}, 
                     {"Output", "FileBrowse", Default = saverDefault.Clip[TIME_UNDEFINED], Save = true},
                     {"Additional Comments", "Text", Default = "", Lines = 2},
                     {"Width", "Slider", Min = 0, Max = 6000, Default = 2048, DisplayedPrecision = 0},
                     {"Height", "Slider", Min = 0, Max = 6000, Default =1556, DisplayedPrecision = 0}
                     })

The next step is to create a new composition using the fusion:Newcomp function. The NewComp function returns the userdata for the composition object created by the function.

oldcomposition = composition
composition = fusion:NewComp()

Then, we must set the active comp to the new one that's just been created.

SetActiveComp(composition)

This will spawn a new composition window.

While we're creating the tools, we should probably also Lock() it so that we prevent the user from accidentally altering the building of the comp. When a comp is locked, the user can't change properties or view anything in the viewers.

Also, they won't be prompted to ask for loader settings and other warnings of that nature.

composition:Lock()

Creating tools through Fusion is simple.

Each tool type is assigned a function that can have a table passed into it with information that will be used to create the tool. For instance, if we wanted to make a background tool and set its width to 2048 and its height to 1556, the following syntax could be used:

BG1 = Background({Width = 2048, Height = 1556})

The Background function returns a variable with the tool's userdata, which is stored in BG1. The tool can then be manipulated with that variable. Let's set its name using the SetAttrs() function.

BG1:SetAttrs({TOOLS_Name = "SlateBG"})

We can then go on setting up each of the tools.

Each of the fields can be manipulated in a similar way.

-- Create a BG ===
BG1 = Background({Width = ret["Width"], Height = ret["Height"]})
 
BG1:SetAttrs({TOOLS_Name = "SlateBG"})
TxtProject = TextPlus({StyledText = "PROJECT:".. ret["Project Name"], Size = 0.05, Center = {0.12, 0.6262}, Width = ret["Width"], Height = ret["Height"], Alignment=1, HorizontalJustification = 0})
 
TxtProject:SetAttrs({TOOLS_Name = "Project"})
 
-- Notice that the Center was defined with a table.
-- The first field is its X value and the second its Y.
MergeProject = Merge({Background = BG1.Output, Foreground = TxtProject.Output})
 
-- The only fields that need to be defined for the Merge are its inputs and outputs
-- unless you've done something to the defaults. 

At this point, you can probably see the direction that the rest of the code is taking. The main caveat is that Savers do not have Foregrounds and Backgrounds; instead, they have an input called "Input" (Saver.Input). Try assembling the next bit yourself. The final code can be seen below.

if tool:GetID() == "Saver" then
        -- Set the Username based on the Windows USERNAME environment variable.
        usernameDefault = fusion:GetEnv("USERNAME")
 
        -- Set the Date
        if os.date("%b") == "May" then
               dateDefault = os.date("%b %d, %Y.")
        else
               dateDefault = os.date("%b. %d, %Y.")
        end
 
        -- Get the default frame range by utilizing the composition's attributes
        framerangeDefault = composition:GetAttrs().COMPN_RenderStart.." - "..composition:GetAttrs().COMPN_RenderEnd
 
        -- parse the saver's Clip
        fileParse = {}
        fileParse = eyeon.parseFilename( tool.Clip[0])
 
        -- see what the last character is.  if it's a separator character, remove it.
        lastChar = string.sub(fileParse.CleanName, string.len(fileParse.CleanName), string.len(fileParse.CleanName))
 
        if lastChar == "." or lastChar == "_" or lastChar == "-" then
               newCleanName = string.sub(fileParse.CleanName,1,string.len(fileParse.CleanName)-1)
        else
               newCleanName = fileParse.CleanName
        end
 
        -- Get the input data from the user.
        ret = comp:AskUser("Slate Options", {{"Project Name", "Text", Default = "Example Project", Lines = 1},
                     {"Shot Name", "Text", Default = newCleanName, Lines = 1 },
                     {"Date", "Text", Default = dateDefault, Lines = 1},
                     {"Artist", "Text", Default = usernameDefault, Lines =1},
                     {"Frame Range", "Text", Default = framerangeDefault, Lines =1},
                     {"Output", "FileBrowse", Default = tool.Clip[0], Save = true},
                     {"Additional Comments", "Text", Default = "", Lines = 2},
                     {"Width", "Slider", Min = 0, Max = 6000, Default = 2048, DisplayedPrecision = 0},
                     {"Height", "Slider", Min = 0, Max = 6000, Default =1556, DisplayedPrecision = 0}
                     })
 
        -- check to see if the user hit 'ok'
        if ret == nil then
               print("Cancelled!")
               return
        end
 
        oldcomposition = composition
        composition = fusion:NewComp()
        SetActiveComp(composition)
 
        composition:Lock()
        wait(2)
 
        BG1 = Background({Width = ret["Width"], Height = ret["Height"]})
        BG1:SetAttrs({TOOLS_Name = "SlateBG"})
        wait(3)
 
        TxtProject = TextPlus({StyledText = "PROJECT: ".. ret["Project Name"], Size = 0.05, Center = {0.12, 0.6262}, Width = ret["Width"],Alignment = 1, Height = ret["Height"],HorizontalJustification=0})
 
        TxtProject:SetAttrs({TOOLS_Name = "Project"})
 
        MergeProject = Merge({Background = BG1.Output, Foreground = TxtProject.Output})
 
        TxtShotName = TextPlus({StyledText = "SHOT NAME: ".. ret["Shot Name"], Size = 0.05, Center = {0.12, 0.5665}, Width = ret["Width"],Alignment = 1, Height = ret["Height"],HorizontalJustification=0})
        TxtShotName:SetAttrs({TOOLS_Name = "ShotName"})
 
        MergeShotName = Merge({Background = MergeProject.Output, Foreground = TxtShotName.Output})
 
        TxtDate = TextPlus({StyledText = "DATE: ".. ret["Date"], Size = 0.05, Center = {0.12, 0.5046}, Width = ret["Width"], Height = ret["Height"],Alignment = 1,HorizontalJustification=0})
        TxtDate:SetAttrs({TOOLS_Name = "Date"})
 
        MergeDate = Merge({Background = MergeShotName.Output, Foreground = TxtDate.Output})
 
        TxtFrameRange = TextPlus({StyledText = "FRAME RANGE: ".. ret["Frame Range"], Size = 0.05, Center = {0.12, 0.4433}, Width = ret["Width"],Alignment = 1 , Height = ret["Height"], HorizontalJustification=0})
        TxtFrameRange:SetAttrs({TOOLS_Name = "Date"})
 
        MergeFrameRange = Merge({Background = MergeDate, Foreground = TxtFrameRange.Output})
 
        TxtComments = TextPlus({StyledText = "COMMENTS: ".. ret["Additional Comments"], Size = 0.05, Center = {0.12, 0.3829}, Width = ret["Width"], Height = ret["Height"],Alignment = 1,HorizontalJustification=0})
        TxtComments:SetAttrs({TOOLS_Name = "Comments"})
        
        MergeComments = Merge({Background = MergeFrameRange.Output, Foreground = Comments})
        
        TxtArtist = TextPlus({StyledText = "ARTIST: ".. ret["Artist"], Size = 0.05, Center = {0.12, 0.3252}, Width = ret["Width"], Height = ret["Height"], Alignment = 1, HorizontalJustification=0})
        TxtArtist:SetAttrs({TOOLS_Name = "Artist"})      
 
        MergeArtist = Merge({Background = MergeComments.Output, Foreground = TxtArtist.Output})
 
        SaverOutput = Saver({Clip = ret["Output"], Input = MergeArtist.Output})
       
        if oldcomposition:GetAttrs().COMPN_RenderStart == 0 then
               renderstart = oldcomposition:GetAttrs().COMPN_RenderEnd +1
        else
               renderstart = oldcomposition:GetAttrs().COMPN_RenderStart - 1
        end
 
        composition:SetAttrs({COMPN_RenderStart = renderstart, COMPN_RenderEnd = renderstart})
 
        -- don't forget to set the composition back after you're done
        -- and unlock it
        composition:Unlock()
 
        SetActiveComp(oldcomposition)
else
        print("ERROR: This is not a saver.")
end


Tips for Tool Scripts and Creating New Comps (edit)

EyeonTips:Script/Tutorials/Slate Manipulation/Tool Scripts and Creating New Comps