Skip to content

Instantly share code, notes, and snippets.

@jeremybep
Last active March 7, 2023 21:07
Show Gist options
  • Save jeremybep/a2123a0afabff53caff9a5db4e996149 to your computer and use it in GitHub Desktop.
Save jeremybep/a2123a0afabff53caff9a5db4e996149 to your computer and use it in GitHub Desktop.
Fusion studio - snippets

Module ScriptLib (scriptapp) de BMD

Python binding de BlackMagic, utile pour traiter une IHM hors fusion ou passer par un Terminal.

  • Fusion 7
import PeyeonScript as eyeon
fusion = eyeon.scriptapp("Fusion")
print fusion
  • Fusion 8
import BlackmagicFusion as bmd
fusion = bmd.scriptapp("Fusion")
print(fusion)
  • Fusion 18
import fusionscript
fusion = fusionscript.scriptapp("Fusion")
print(fusion)

On peut remarquer, par héritage, que seul le binding d'import change entre les versions. La plupart du code qui suit est issue de Fusion 7, qui lui même est issue du feu Digital Fusion (eyeon software). Toutes fonction sont présentes et la documentation de 2010 est encore bonne à prendre :)

https://www.steakunderwater.com/VFXPedia/96.0.243.189/index5522.html?title=Eyeon:Script/Reference/Applications/Fusion/Classes/Fusion

Mais rassurez vous, il a aussi le manuel de scripting :

/Applications/Blackmagic Fusion 18/Developer/Fusion 8 Script Manual.pdf

FusionPath & FuScript

Utiliser FuScript depuis un Terminal

win : C:\Program Files\Blackmagic Design\Fusion 9\FuScript.exe

linux : /opt/BlackmagicDesign/Fusion9/fuscript

darwin : /Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fuscript

Arguments :

fuscript
Usage: fuscript [opts] <script> [args]
   -i                   - Enter interactive mode
   -v                   - Print version information
   -s                   - Run as script server
   -S                   - Run as script server with no timeout
   -p [appname]         - Ping script apps
   -P [appname]         - Ping script apps (longer timeout)
   -q                   - Be quiet
   -b                   - Run with commandline debugger
   -l <lang>            - Explicitly set language, from:
      lua
      py2 or python2
      py3 or python3
   -x <string>          - Execute string

Exécuter une chaîne localement et imprime les résultats dans l'onglet Console

# You can use FuScript to send a python command to the active Fusion session:
fuscript -l py2 -x 'fusion = fusionscript.scriptapp("Fusion");comp = fusion.CurrentComp;import sys; comp.Print("[Python Version] " + str(sys.version) + "\n")'

Connection à distance via hostname

# You can use FuScript to send a remote python command:
fuscript -l py2 -x 'fusion = fusionscript.scriptapp("Fusion", "hostname");comp = fusion.CurrentComp;import sys; comp.Print("[Python Version] " + str(sys.version) + "\n")'

Executer un script (.lua) à diatnce sur "hostname"

# You can use FuScript to run 
fuscript -l py2 -x 'fusion = fusionscript.scriptapp("Fusion", "Pine");comp = fusion.CurrentComp;' <script.py>

Utils

Vérouiller la composition

S'il y a bien une chose à connaitre dans le process de fusion, c'est le lock()/unlock() Il permet de verrouiller la comp et ansi éviter des prompt sur de lourd/long traitements. ex: changement de PATH, évite les popup windows de replacements

comp.Lock()
... do stuff ...
comp.Unlock()

Obtenir/Assignez des Attributs

# Récupère tout les attrs de la comp (simple)
from pprint import pprint
pprint(comp.GetAttrs())
# Récupère tout les attrs des comp ouvertes (multiple)
from pprint import pprint
print("[Active Fusion Comps]")
compList = fu.GetCompList()
for key, cmp in sorted(compList.items()):
    print("[" + str(cmp.GetAttrs()["COMPS_Name"]) + "]")
    pprint(cmp.GetAttrs())
    print("\n")

Ce processus de fonction comp.GetAttrs () [""] le nom de l'attribut individuel entre crochets.

Get/Set

print(comp.GetAttrs('SOME_ATTR'))
print(comp.SetAttrs('SOME_ATTR'))

Frame Range

renderStart = int(comp.GetAttrs()["COMPN_RenderStart"])
renderEnd = int(comp.GetAttrs()["COMPN_RenderEnd"])
stepBy = int(comp.GetAttrs()["COMPI_RenderStep"])
print("[Render Frame Range] " + str(renderStart) + "-" + str(renderEnd) + " [Step by Frames] " + str(stepBy) + "\n")

préférence de composition

print(comp.GetPrefs())
comp.SetPrefs({})

Example

#rajout d'une ligne dans Fusion.prefs (#MASTER PROFIL)
#C:\Users\%USERNAME%\AppData\Roaming\Blackmagic Design\Fusion\Profiles\Default
#Pour évuentuel copy des pref ["Global"]
comp = self.fusion.GetCurrentComp()
comp.SetPrefs({
    "Comp.FrameFormat.Name": "HDTV 1080",
    "Comp.FrameFormat.Width": 1920,
    "Comp.FrameFormat.Height": 1080,
    "Comp.FrameFormat.AspectX": 1.0,
    "Comp.FrameFormat.AspectY": 1.0,
    "Comp.FrameFormat.GuideRatio": 1.77777777777778, 
    "Comp.FrameFormat.Rate": 25,
    "Comp.FrameFormat.DepthInteractive": 2,   #16 bits Float
    "Comp.FrameFormat.DepthFull": 2,          #16 bits Float
    "Comp.FrameFormat.DepthPreview": 2        #16 bits Float
    })

Saver (format exr)

#paramétrer le format exr
if tool.GetAttrs('TOOLS_Name') == 'exr':
    tool.OpenEXRFormat.Depth = 1  # 16-bit float
    tool.OpenEXRFormat.ProcessRed = 1
    tool.OpenEXRFormat.ProcessGreen = 1
    tool.OpenEXRFormat.ProcessBlue = 1
    tool.OpenEXRFormat.ProcessAlpha = 0
    tool.OpenEXRFormat.RedEnable = 1
    tool.OpenEXRFormat.GreenEnable = 1
    tool.OpenEXRFormat.BlueEnable = 1
    tool.OpenEXRFormat.AlphaEnable = 0
    tool.OpenEXRFormat.ZEnable = 0
    tool.OpenEXRFormat.CovEnable = 0
    tool.OpenEXRFormat.ObjIDEnable = 0
    tool.OpenEXRFormat.MatIDEnable = 0
    tool.OpenEXRFormat.UEnable = 0
    tool.OpenEXRFormat.VEnable = 0
    tool.OpenEXRFormat.XNormEnable = 0
    tool.OpenEXRFormat.YNormEnable = 0
    tool.OpenEXRFormat.ZNormEnable = 0
    tool.OpenEXRFormat.XVelEnable = 0
    tool.OpenEXRFormat.YVelEnable = 0
    tool.OpenEXRFormat.XRevVelEnable = 0
    tool.OpenEXRFormat.YRevVelEnable = 0
    tool.OpenEXRFormat.XPosEnable = 0
    tool.OpenEXRFormat.YPosEnable = 0
    tool.OpenEXRFormat.ZPosEnable = 0
    tool.OpenEXRFormat.XDispEnable = 0
    tool.OpenEXRFormat.YDispEnable = 0

Listing des entrées nodal

object.GetInputList()
print(object.GetInputList())

General Path

#LOADER
loader = comp.Loader({'Clip' : 'C:\\Path_To_File.jpg'})

#MATERIAL
mat = comp.MtlBlinn({
    'Diffuse.Color.Material' : 'C:\\Path_To_File.jpg',
    'Specular.Intensity.Material' : 'C:\\Path_To_File.jpg'
    })

#GEOMETRY
geo = comp.SurfaceFBXMesh({'ImportFile' : 'C:\\Path_To_File.fbx', 'ObjName' : 'Sphere', 'Size' : 0.5})

#TEXTURE
disp_tex = comp.Loader({'Clip' : 'C:\\Path_To_File.jpg'})
disp = comp.Displace3D({'Scale' : 7.5, 'Bias' : -0.5, 'Input' : disp_tex, 'SceneInput' : geo})

Split

Exemple avec nom de fichier du type : la_tete_a_toto.comp

PATH_NOM_de_ma_comp = self.comp.GetAttrs("COMPS_FileName")
BASENAME_de_ma_comp = os.path.basename(PATH_NOM_de_ma_comp)
BASENAME_SPLIT_de_ma_comp = os.path.splitext(BASENAME_de_ma_comp)[0]
SPLIT_A = BASENAME_SPLIT_de_ma_comp.split("_")[0]                       
SPLIT_B = BASENAME_SPLIT_de_ma_comp.split("_")[1]                       
SPLIT_C = BASENAME_SPLIT_de_ma_comp.split("_")[2]                    
SPLIT_D = BASENAME_SPLIT_de_ma_comp.split("_")[3]
 
print SPLIT_A
print SPLIT_B
print SPLIT_C
print SPLIT_D

Console :

>>> la
>>> tete
>>> a
>>> toto

Il existe aussi la fonction fusionscript.split() ou fusionscript.parseFilename()

vous pouvez retrouver toutes ces méthodes dans la scriptlib : C:\Program Files\Blackmagic Design\Fusion 9\Scripts\bmd.scriptlib

Refresh/Update node

Il s’agit d'activer/désactiver un node via ce Toggle :

tool.SetAttrs({'TOOLB_PassThrough' : True})
tool.SetAttrs({'TOOLB_PassThrough' : False})

3D

Material

mat = comp.MtlBlinn({})
mat.Diffuse.Color.Material = diff_tex
mat.Specular.Intensity.Material = spec_tex

Geometry

geo = comp.SurfaceFBXMesh({})
geo.ImportFile = 'C:\\Path_To_File.fbx'
geo.ObjName = 'Sphere'
geo.Size = 0.5
geo.MaterialInput = mat

Shader/Displace

disp = comp.Displace3D({})
disp.Input = disp_tex
disp.SceneInput = geo
disp.Scale = 7.5
disp.Bias = -0.5

NormalBump

norm = comp.Loader({'Clip' : 'C:\\Path_To_File.jpg'})
norm_tex = comp.BumpMap({})
norm_tex.Input = norm
norm_tex.SourceImageType = 1
norm_tex = comp.BumpMap({'Input' : norm, 'SourceImageType' : 1})

FBX

fus_geo = 'C:\\Path_To_File.fbx'
 
geo_out = comp.ExporterFBX({})
geo_out.Input = disp_tex
geo_out.Filename = fus_geo

Format et Attrs Geo

print(geo_out.Format.GetAttrs())
print(geo_out.Format.GetAttrs()['INPIDT_ComboControl_ID'])
 
geo_out.Format = 'FBX_binary'
geo_out.Format = '_3D_Studio_3DS'
geo_out.Format = 'Alias_OBJ'

Viewer

L_view = comp.GetPreviewList()["Left"]
L2_view = comp.GetPreviewList()["Left.B"]
R_view = comp.GetPreviewList()["Right"]
R2_view = comp.GetPreviewList()["Right.B"]

Afficher l'image dans le viewer

L_view.ViewOn(image)

Clean du viewer

L_view.ViewOn(0)

Mise à l'échelle

(50% = .5, 100% = 1, Fit = 0)

L_view.View.SetScale(.5)
L_view.View.SetScale(1)
L_view.View.SetScale(0)

Comparaison A/B view

L_view.View.SetBuffer(2)

Split

L_view.View.SetSplit(.5, .5, 45)

Reset

L_view.View.ResetView()

QuadView

L_view.View.ShowingQuadView()

Revoie : True ou False

L_view.View.ShowQuadView(True)
L_view.View.ShowQuadView(False)

Sub view

L_view.View.ShowingSubView()

Revoie : True ou False

L_view.View.ShowSubView(True)
L_view.View.ShowSubView(False)

Swap sub view

L_view.View.SwapSubView()

Exemples :

Exemple de switch

comp.CurrentFrame.ViewOn(image, 1) # image sur la vue de gauche.
comp.CurrentFrame.ViewOn(image, 2) # image sur la vue de droite.
comp.CurrentFrame.ViewOn(1) #actif sur la vue.
comp.CurrentFrame.ViewOn(1, 0) # nettoie vue gauche.
comp.CurrentFrame.ViewOn() # nettoie toutes les vues

Exemple avec les modules du Viewer

comp.CurrentFrame.SwitchMainView('ToolView')
comp.CurrentFrame.SwitchMainView('SplineEditorView')
comp.CurrentFrame.SwitchMainView('TimelineView')
comp.CurrentFrame.SwitchMainView('TransportView')
comp.CurrentFrame.SwitchMainView('ModifierView')
comp.CurrentFrame.SwitchMainView('MaskView')
comp.CurrentFrame.SwitchMainView('TimeRulerView')
comp.CurrentFrame.SwitchMainView('ConsoleView')
comp.CurrentFrame.SwitchMainView('InfoView')
comp.CurrentFrame.SwitchMainView('FlowView')
comp.CurrentFrame.SwitchMainView('CurrentView')
comp.CurrentFrame.SwitchMainView('Composition')
comp.CurrentFrame.SwitchControlView('ControlView')
comp.CurrentFrame.SwitchControlView('ModifierView')

LUT

L_view.View.EnableLUT()
L_view.View.IsLUTEnabled()

Revoie : True ou False


Flow nodal

Choix du node:

object = comp.Loader({})

# sélectionnez le flow
flow = comp.CurrentFrame.FlowView
 
# Choisir l'objet
flow.Select(object)
flow.Select(object, True)
 
# Rejetez le choix
flow.Select(object, False)
 
# Sélection du node actif
comp.SetActiveTool(object)
 
#S'il est actif ont récupère ses propriétés
print(comp.GetAttrs('COMPH_ActiveTool'))
print(comp.ActiveTool())
 
Déplacament du node
# Choix du flow
flow = comp.CurrentFrame.FlowView
 
# Sélection du node
object = comp.Loader({'Clip' : 'C:\\Temp\\image.png'})
 
# Assign sa position
flow.SetPos(object, 5, 5)
 
# Connaitre sa position
flow.GetPos(object) # il renvoie seulement X position
flow.GetPosTable(object) # il renvoie X et Y comme dictionnaire

Groupement de node

# Créér groupe
group = comp.GroupOperator({})
 
# Assigner un Nom au groupe
group.SetAttrs({'TOOLS_Name': 'GroupName'})
 
# Ajouter node au groupe
object.SetAttrs({'TOOLH_GroupParent': group})

Render

Calculer les images de la compo

comp.Render(True, 0, 1) # Render(wait_for_render, renderstart, renderend, step, proxy, hiQ, mblur)
 
# Start time
comp.SetAttrs({'COMPN_RenderStart': 0.0}) # int = render start time in frames
 
# End time
comp.SetAttrs({'COMPN_RenderEnd': 50}) # int = render end time in frames
 
# Global start time
comp.SetAttrs({'COMPN_GlobalStart': 10}) # int = global start time in frames
 
# Global end time
comp.SetAttrs({'COMPN_GlobalEnd': 10}) # int = global end time in frames
 
# Calculer en "silent Mode" (backgound rendering)
comp.SetAttrs({'COMPB_Rendering': True})
comp.SetAttrs({'COMPB_Rendering': False})
 
# Verbose du calcul
print(comp.GetAttrs('COMPN_RenderStartTime'))
print(comp.GetAttrs('COMPN_RenderEndTime'))

Cas concret & exemple :

- calcul de la compo

start_frame = self.comp.SetAttrs({'COMPN_GlobalStart' : newGlobalIn})
end_frame = self.comp.SetAttrs({'COMPN_GlobalEnd' : newGlobalOut})
rnd_start_frame = self.comp.SetAttrs({'COMPN_RenderStart': newGlobalIn})
rnd_end_frame = self.comp.SetAttrs({'COMPN_RenderEnd': newGlobalOut})

- set Frame range via un loader

loaderName = "name of your loader"
 
toollist = comp.GetToolList().values()
for tool in toollist:
    if tool.GetAttrs("TOOLS_RegID") == "Loader":
         if tool.GetAttrs('TOOLS_Name') == loaderName:
            newGlobalIn = int(tool.GlobalIn[0])
            newGlobalOut = int(tool.GlobalOut[1])
        else:
            print (loaderName,  "not found !")
 
start_frame = comp.SetAttrs({'COMPN_GlobalStart' : newGlobalIn})
end_frame = comp.SetAttrs({'COMPN_GlobalEnd' : newGlobalOut})
rnd_start_frame = comp.SetAttrs({'COMPN_RenderStart': newGlobalIn})
rnd_end_frame = comp.SetAttrs({'COMPN_RenderEnd': newGlobalOut})

- set Attribute loader

toollist = comp.GetToolList().values()
        tool = []
        for tool in toollist:
            if tool.GetAttrs("TOOLS_RegID") == "Loader":
                loaderPath = tool.GetAttrs("TOOLST_Clip_Name")
                loaderPathClean = loaderPath[1]

                # Extract, split, via ".split(os.sep)" require import os
                loaderPathSplit = loaderPathClean.split(os.sep)

                # Listing files
                LoaderPathDir = os.listdir(loaderPathSplit[0])

                self.comp.Lock() # lock comp durring loop operation

                #refresh loader
                tool.SetAttrs({'TOOLB_PassThrough' : True})
                tool.SetAttrs({'TOOLB_PassThrough' : False})


                # Check file in directory, then apply frame missing,
                LoaderPathDir = os.listdir(reLoadPath)

                if len(LoaderPathDir) == 0:
                    # Write comment on loader
                    tool.Comments = "NO FOOTAGE"

                    # select missing frame : 0: Fail, 1:Hold previous, 2: Output Color, 3: Wait
                    tool.MissingFrames = 2

                    # Add tile color to the loader for warning users
                    tool.TileColor = {'R' : 255/255, 'G' : 85/255, 'B' : 0/255}

               # If got one file to the directory put loop
                if len(LoaderPathDir) == 1:
                    tool.Loop

                # If TOOLS_Name is good then force frame in - frame out
                if tool.GetAttrs('TOOLS_Name') == "we_suck_less":

                    # don't forget to force integer
                    newGlobalIn = int(tool.GlobalIn[0])
                    newGlobalOut = int(tool.GlobalOut[1])

                    self.comp.Unlock()

- ouvrir une comp

    # Load a composite
    compFilepath = fusion.MapPath("Temp:/example.comp")
    cmp = fusion.LoadComp(compFilepath)
    # The comp.Print() command will write the result into the Console tab of the active composite
    cmp.Print("[Opened Comp] " + str(cmp.GetAttrs()["COMPS_FileName"]))

- sauvegarder une comp

# Generate a date and time stamp
import datetime
currentDate = datetime.datetime.now().strftime("%Y-%m-%d %H.%M %p")

# Generate a filename with a date and time stamp on it that is going to be written to the %TEMP% / $TMPDIR folder on your system
compFilepath = fusion.MapPath("Temp:/" + str(currentDate) + " example.comp")

# Save the comp to disk
comp.Save(compFilepath)

# The comp.Print() command will write the result into the Console tab of the active composite
comp.Print("[Saved Comp] " + str(comp.GetAttrs()["COMPS_FileName"]))

- listing des comp ouvertes

  from pprint import pprint
  compList = fusion.GetCompList()
  pprint(compList.items())
  • Balancer les comp ouvertes dans un dict
# Scan the list of comp items
compList = fu.GetCompList()
for key,value in sorted(compList.items()):
print key, value

- fermez toutes les comp

print("[Close All Comps]")
compList = fu.GetCompList()
for key, cmp in sorted(compList.items()):
    print(str(cmp.GetAttrs()["COMPS_FileName"]))

    # If the comp is unlocked, it will ask if the comp should be saved before closing.
    cmp.Unlock()

    # Close the active comp
        cmp.Close()

- attributs du programme de fusion

# Check out the Fusion Attributes
from pprint import pprint
pprint(fu.GetAttrs())

Attributs des Class Fusion : Eyeon:Script/Reference/Applications/Fusion/Classes/Fusion - VFXPedia

- importer une macro dans le comp actuelle

# Stop Loader/Saver node file dialogs from showing
comp.Lock()

# Translate a relative PathMap location into a filepath:
macroFilePath = comp.MapPath('Macros:/example.setting')
print('[Macro File] ' + macroFilePath)

# Read the macro file into a variable
macroContents = bmd.readfile(macroFilePath)
print('[Macro Contents]\n' + str(macroContents))

# Add the macro to your foreground comp
comp.Paste(macroContents)

# Allow Loader/Saver node file dialogs to show up again
comp.Unlock()

- récupérer/Attribuer les valeurs d'un node

Récupérer le texte du node Text1 :

textNode = comp.Text1()
print textNode.GetInput("StyledText")
print textNode.GetInput("Size")

Pour injecter une nouvelle valeur:

textNode({'StyledText' : 'bla bla bla'})

Récupérer les metadata du Loader1

metadata = comp.Loader1.Output.GetValue().Metadata
for field in metadata:
    print field
print metadata['worldToNDC']

- dossier de la comp actuelle :

compdir = os.path.dirname(comp.GetAttrs()['COMPS_FileName'])

- désactiver tous les savers :

# methode decomposer
toollist = comp.GetToolList().values()
    for tool in toollist:
        if (tool.GetAttrs()['TOOLS_RegID']) == "Saver":
            tool.SetAttrs({'TOOLB_PassThrough': True})

- trouver un node (inline) :

# recuperer tous les saver
all_savers = comp.GetToolList(False, "Saver").values()

- widths and heights resolution info :

selectedTools = comp.GetToolList(True)
loaders=[]
widths = []
heights = []
for tool in selectedTools.values():
    if tool.GetAttrs()['TOOLS_RegID'] =='Loader': 
        loaders.append(tool)
        widths.append(tool.GetAttrs('TOOLIT_Clip_Width')[1])
        heights.append(tool.GetAttrs('TOOLIT_Clip_Height')[1])

- callOut script

import subprocess
subprocess.Popen(["C:/Program Files/eyeon/Fusion x.x/eyeonScript.exe", "x:/path/of/your/cripts/YourScript.eyeonscript"], shell=True, stdout=subprocess.PIPE) 

- trouver tous les loader dans la comp

#Récupérer le PATH de tous les Loaders de la comp
toollist = self.comp.GetToolList().values()
for tool in toollist:
    if tool.GetAttrs("TOOLS_RegID") == "Loader":
        ...Do stuff...

afficher le saver sur les deux vues avec un "fit"

for tool in toollist:
    if tool.GetAttrs('TOOLS_Name') == 'nodeName':
        #flow = self.comp.CurrentFrame.FlowView
        #flow.Select(tool)
        L_view = self.comp.GetPreviewList()["Left"]
        R_view = self.comp.GetPreviewList()["Right"]
        L_view.ViewOn(tool)
        L_view.View.SetScale(0)
        R_view.ViewOn(tool)
        R_view.View.SetScale(0)

support

https://www.steakunderwater.com/wesuckless/donate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment