Extrait de code Guerilla

Documentation officielle de l’API Python

Il y a quelques exemples dans la section Examples de la documentation officielle de l’API Python.

Utilisation de la console

La console Guerilla est très utile. N’hésitez pas à l’utiliser !

Ouvrez-la depuis "View"/"Show/Hide console" :

Guerilla show/hide consolet

Une fois dans la console, vous pouvez créer un onglet Python via File/New/Python ou directement via le raccourci clavier Ctrl+Shift+N. Vous pouvez aussi sauvegarder votre fichier Python pour le conserver pour la prochaine session.

Assurez-vous que l’option "View"/"Command Echo" est activée de sorte que Guerilla affiche les commandes (lua) qu’il exécute quand vous travaillez depuis la UI (à la Maya). Cela permet de récupérer les noms des attributs que vous modifiez (plugs) plus facilement.

Par exemple, quand vous changer la valeur de l’attribut Filter (dans la section Pixel Filter d’une RenderPass), vous verrez :

local mod=Document:modify()
mod.set(_"RenderPass.PixelFilter","triangle")
mod.finish()

C’est le code lua exécuté par Guerilla. Il crée un modificateur mod, l’utilise pour modifier la valeur et le « ferme ». « _ » est un alias de pynode().

En Python, un tel code devient :

import guerilla

with guerilla.Modifier() as mod:
    guerilla.pynode("RenderPass.PixelFilter").set("triangle")
    # ou
    guerilla.pynode("RenderPass").PixelFilter.set("triangle")
    # ou encore
    guerilla.Document().RenderPass.PixelFilter.set("triangle")

Les trois lignes font la même chose.

Contexte de modification

Comme vous avez pu le remarquer dans la section précédente, Guerilla a besoin d’un contexte de modification pour modifier les nœuds correctement :

local mod=Document:modify()
-- do some stuff
mod.finish()

L’API Python s’appuie sur l’instruction with et le contexte guerilla.Modifier(). Cela veut dire que l’équivalent Python au code lua ci-dessus est :

with guerilla.Modifier() as mod:
   # do some stuff

Cela a l’inconvénient d’utiliser une tabulation. Vous pouvez tricher en utilisant directement les méthodes du contexte :

mod = guerilla.Modifier()
mod.__enter__()
# do some stuff
mod.__exit__(None, None, None)

Plus d’information disponible ici.

Récupérer le type d’un nœud depuis la UI

Vous pouvez obtenir le type d’un nœud depuis l’onglet Node List de Guerilla. Le type du nœud est affiché à droite de son nom :

Guerilla node list

Ici, on voit que Beauty est un nœud de type LayoutOut.

Récupérer un nœud depuis son nom/chemin

Guerilla fournie deux façons de récupérer un nœud depuis son nom (ou son chemin) :

guerilla.pynode("Toto")
guerilla._("Toto")

Les deux renvoient une instance du nœud Toto. La différence étant que le second renvoie None si le nœud n’existe pas, alors que le premier lève une exception de type ValueError.

Information sur un nœud

Afficher des informations sur le nœud sélectionné :

import guerilla

# reference vers le document Guerilla
doc = guerilla.Document()

for node in doc.selection():
    print "Type:", type(node)
    print "Name:", node.name
    print "Path:", node.path
    print "Parent's name:", node.parent.name
    print "Plugs:"
    for plug in node.plugs():
        print "  ", plug.name

Mettre un nœud à l’intérieur d’un autre

Ce code met le nœud a à l’intérieur du nœud b :

with guerilla.Modifier() as mod:
    a.move(b)

Assurez-vous que a est déconnecté des autres nœuds avant de le déplacer.

Itérer à travers les RenderPass/Layer/AOV

import guerilla

doc = guerilla.Document()

for render_pass in doc.children(type='RenderPass'):
    print render_pass.name
    for layer in render_pass.children(type='RenderLayer'):
        print layer.name
        for aov in layer.children(type='LayerOut'):
            # le nom affiché de l'aov est différent du nom du noeud
            print aov.name, aov.PlugName.get()

Sur une nouvelle scène, vous aurez :

RenderPass
Layer
Input1 Beauty

Récupérer et définir des valeurs d’attributs

In Guerilla, node attributes are of type guerilla.Plug and you get/set values using get() and set() method.

Dans Guerilla, les attributs des nœuds sont de type guerilla.Plug (documentation disponible ici) et vous Récupérez et définissez leur valeur en utilisant respectivement les méthodes get() et set().

Changer la résolution globale

import guerilla

doc = guerilla.Document()

with guerilla.Modifier() as mod:
    doc.ProjectWidth.set(320)
    doc.ProjectHeight.set(240)

Vous pouvez diviser la résolution par deux de cette façon :

import guerilla

doc = guerilla.Document()

with guerilla.Modifier() as mod:
    doc.ProjectWidth.set(doc.ProjectWidth.get()/2)
    doc.ProjectHeight.set(doc.ProjectHeight.get()/2)

Activer et désactiver "Use Project Settings" d’une RenderPass

Guerilla project size

Quand vous cliquez sur la case à cocher Use Project Settings d’une RenderPass, Guerilla quelques opérations sous le capot :

import guerilla

doc = guerilla.Document()

# part du principe que vous avez selectionne une RenderPass
rp = doc.selection()[0]

# deconnecte les valeurs de Project Settings
with guerilla.Modifier() as mod:
    rp.Width.disconnect(doc.ProjectWidth)
    rp.Height.disconnect(doc.ProjectHeight)
    rp.AspectRatio.disconnect(doc.ProjectAspectRatio)

# reconnecte les valeurs a Project Settings
with guerilla.Modifier() as mod:
    rp.Width.connect(doc.ProjectWidth)
    rp.Height.connect(doc.ProjectHeight)
    rp.AspectRatio.connect(doc.ProjectAspectRatio)

Savoir si deux nœuds sont connectés, dans n’importe quel ordre

def are_connected(a, b):
    # on teste si les noeuds sont connecte dans un sens...
    for out in a.getoutputs():
        for in_ in b.getinputs():
            if in_.Plug.isconnected(out.Plug):
                return True

    # ...et dans l'autre
    for out in b.getoutputs():
        for in_ in a.getinputs():
            if in_.Plug.isconnected(out.Plug):
                return True

    # on n'a trouve aucune connection
    return False

Sélectionnez deux nœuds puis lancez :

# recupere les deux premiers noeuds de la selection
a, b = guerilla.Document().selection()[0:2]
print are_connected(a, b) # devrait afficher True

Itérer à travers tous les nœuds entrant dans un nœud donné

def input_connected_nodes(node):

    for in_ in node.getinputs():

        connected_plug = in_.getconnected()

        if not connected_plug:
            continue

        connected_node = connected_plug.parent

        yield connected_node

        # on fait ca recursivement
        input_connected_nodes(connected_node)

Sélectionnez un nœud connecté à d’autres :

Guerilla iterate input nodes

Puis exécutez :

node = guerilla.Document().selection()[0]
print [n.name for n in input_connected_nodes(node)]
# ['Trace', 'Surface', 'All']

Vous pouvez modifier cette fonction pour qu’elle fonctionne avec les nœuds sortants en remplaçant la méthode getinputs() par getoutputs().

Itérer à travers les nœuds à la gauche d’un nœud donné

import guerilla

def left_nodes(node):
    # recupere la position du noeud source
    src_x, src_y = node.NodePos.get()

    # itere a travers tous les noeuds freres
    for n in node.parent.children():

        # recupere la position
        x, _ = n.NodePos.get()

        # renvoie le noeud uniquement s'il se trouve a gauche du noeud source
        if x < src_x:
            yield n

Sélectionner un nœud dans un RenderGraph puis exécutez :

node = guerilla.Document().selection()[0]
print [n.name for n in left_nodes(node)]

Ceci affichera le nom de tous les nœuds positionné à gauche du nœud sélectionné.

Connecter un nœud entre deux autres

Cette fonction connectera le nœud b entre a et c de sorte que a sera connecté à b, lui-même connecté à c.

Elle part du principe que :

Cela permet de connecter dynamiquement des Binop.

import guerilla

def put_between(a, b, c):

    # on part du principe que les noeuds n'ont qu'une entree et sortie
    a_out = a.getoutputs()[0].Plug
    b_in  = b.getinputs()[0].Plug
    b_out = b.getoutputs()[0].Plug
    c_in  = c.getinputs()[0].Plug

    # deconnecte a et c s'ils sont connecte
    if c_in.isconnected(a_out):
        c_in.disconnect(a_out)

    b_in.connect(a_out)
    c_in.connect(b_out)

Sélectionnez trois nœuds dans l’ordre de connexion voulu :

Guerilla put between 001

a,b,c = guerilla.Document().selection()[0:3]

# il vous faut un contexte de modification, car vous allez modifier le graph
with guerilla.Modifier() as mod:
    put_between(a, b, c)

Guerilla put between 002

Essayez d’améliorer le code pour bouger le nœud c de sorte qu’il se place entre a et b. ;)

Frame (ressemblant au backdrop Nuke)

Créer une frame

Ceci cré un nœud GraphFrame :

import guerilla

# recupere le noeud RenderGraph
rg = guerilla.pynode("RenderGraph")

# il vous faut un contexte de modification, car vous modifiez le gproject
with guerilla.Modifier() as mod:
    frame = mod.createnode("NewFrame", type="GraphFrame", parent=rg)

    # vous pouvez personnaliser la frame
    frame.Notes.set("TOTO!")
    frame.Position.set((-200, -50))
    frame.Size.set((200, 150))
    frame.Color.set((1.0, 0.5, 0.6))
    frame.Opacity.set(0.2)

Guerilla create frame

Itérer à travers les nœuds positionnés dans une GraphFrame donné

def nodes_in_frame(frame):

    # on recupere la position et la taille
    p_x, p_y = frame.Position.get()
    s_x, s_y = frame.Size.get()

    # on calcule les bords de la boite
    x_min = p_x
    x_max = p_x+s_x
    y_min = p_y
    y_max = p_y+s_y

    # notez qu'on itere uniquement a travers les noeuds heritant du type
    # 'RenderGraphNode', car ils disposent de l'attribut'NodePos'.
    for node in frame.parent.children(type='RenderGraphNode'):

        n_x, n_y = node.NodePos.get()

        # test si la position du noeud est dans la frame
        if all((x_min < n_x < x_max,
                y_min < n_y < y_max)):
            yield node

Si nous utilisons la variable frame créé dans l’exemple précédent :

print [n.name for n in nodes_in_frame(frame)]
# ['Layer', 'Output']

Savoir si un nœud est à l’intérieur d’une frame

Il s’agit d’une variation de la fonction précédente. Celle-ci renvoi True si le nœud donné est à l’intérieur de la frame donnée.

def is_node_in_frame(node, frame):

    # on recupere la position et la taille
    p_x, p_y = frame.Position.get()
    s_x, s_y = frame.Size.get()

    # on calcule les bords de la boite
    x_min = p_x
    x_max = p_x+s_x
    y_min = p_y
    y_max = p_y+s_y

    n_x, n_y = node.NodePos.get()

    # test si la position du noeud est dans la frame
     return all((x_min < n_x < x_max,
                 y_min < n_y < y_max))

Avec le GraphFrame cré précédemment :

node = guerilla.pynode("RenderGraph|Layer")
print node_in_frame(node, frame)
# True

Créer une macro à l’intérieur d’un RenderGraph

Voici un bout de code permettant de créer une macro en script de la même façon dont Guerilla le fait quand on s’y prend manuellement :

Guerilla new macro

import guerilla

rg = guerilla.pynode("RenderGraph")

with guerilla.Modifier() as mod:
    macro = mod.createnode("NewMacro", type="RenderGraphMacro", parent=rg)
    out = mod.createnode("Output", type="RenderGraphMacroOutput", parent=macro)
    in_ = mod.createnode("Input1", type="RenderGraphInput", parent=out)
    out2 = mod.createnode("Output1", type="RenderGraphOutput", parent=macro)
    out2.Plug.adddependency(in_.Plug)
    in_.PlugName.connect(out2.PlugName)

Créer un sub shader

Ce bout de code créer un subshader sur le paramètre Dirt du matériau Surface par défaut :

Guerilla sub shader 001

Guerilla sub shader 002

import guerilla

# materiau "Surface" par defaut
surf_shader = guerilla.pynode("RenderGraph|Surface")

with guerilla.Modifier() as gmod:

    # cree un sub shader vide sur l'attribut "Dirt"
    sub_shader = gmod.createnode("Dirt", type="ShaderNodeMacro", parent=surf_shader)

    # cree un noeud de sorti dans le sub shader
    main_out = gmod.createnode("Output", type="ShaderNodeMacroOutput", parent=sub_shader)
    main_out.NodePos.set((-90,-17.5))

    # cree l'entre du noeud de sorti
    in_node = gmod.createnode("Input1", type="ShaderNodeIn", parent=main_out)
    in_node.Desc.set(guerilla.types('color'))
    in_node.Value.set((0.5, 0.0, 0.5))

    # cree la sortie cache du noeud de sorti principal
    out_plug = gmod.createnode("Output1", type="ShaderNodeOut",parent=sub_shader)
    out_plug.PlugName.set("Output")

    gmod.adddependency(out_plug.Plug, in_node.Plug)
    gmod.connect(in_node.PlugName, out_plug.PlugName)

Savoir si un nœud a un attribut d’entrée ou de sorti avec le nom donné

L’argument de la méthode hasPlug() doit être le nom de la plug, mais ce nom peut être différent du nom affiché dans la UI. Ce dernier étant stocké dans l’attribut PlugName. Cette fonction permet de vérifier qu’une plug existe suivant la valeur de PlugName.

def has_plug_name(node, name, inputs=True, outputs=True):
    '''Return if the given node has plug with PlugName attribute with the given
    name.

    :param (guerilla.Node) node:
    Guerilla node to check for inputs/outputs names

    :param (str) name:
    The plug name to look for

    :param (bool) inputs:
    If True, will check for input plug names

    :param (bool) outputs:
    If True, will check for output plug names
    '''
    if inputs:
        for i in node.getinputs():

            if i.PlugName.get() == name:
                return True

    if outputs:
        for o in node.getoutputs():

            if o.PlugName.get() == name:
                return True

    return False

Maintenant, vérifions que le nœud par défaut Trace a un attribut d’entrée set :

Guerilla default RenderGraph

import guerilla

rg = guerilla.pynode("RenderGraph")
print rg.hasPlug("set")
# False
print has_plug_name(rg.Trace, name="set", inputs=True, outputs=False)
# True

Récupérer le nœud connecté à l’attribut donné d’un nœud donné

Cette fonction renvoie le nœud connecté à l’entrée ou la sortie du nœud donné ayant le nom donné. Il est important de comprendre que s’il y a plusieurs nœuds d’entrées ou de sorties avec le nom donné, seul le premier est donné (cela se produit rarement, mais il faut en être conscient) :

def connected_node(node, name, inputs=True, outputs=True):

    if inputs:
        for i in node.getinputs():

            if i.PlugName.get() == name:

                 con_out = i.getconnected()

                 if con_out:

                     return con_out.parent

    if outputs:
        for o in node.getoutputs():

            if o.PlugName.get() == name:

                 for con_in in o.getconnected():

                     return con_in.parent

Ici, nous récupérons le nœud connecté à l’attribut d’entrée set du nœud Trace :

Guerilla default RenderGraph

import guerilla

rg = guerilla.pynode("RenderGraph")
node = connected_node(rg.Trace, name="set", inputs=True, outputs=False)
print node.name
# "Surface"

Comme dis précédemment, dans le cas des nœuds de sortis, cette fonction ne renvoie que le premier nœud de sorti trouvé. Vous pouvez modifier cette fonction de sorte qu’elle renvoie une liste au lieu de s’en tenir au premier.

Créer le nœud de RenderGraph par défaut

L’API Python de Guerilla ne fournis pas de fonction permettant la création du nœud de RenderGraph par défaut, il faut donc l’écrire vous-même :

import guerilla

def create_render_graph():

    doc = guerilla.Document()

    # create render graph
    rg = guerilla.Node.create('RenderGraph', type='RenderGraph', parent=doc)

    # tag node
    all_ = guerilla.Node.create('All', type='RenderGraphNodeTag', parent=rg)
    all_.Tag.set('All')
    all_.Lights.set(True)
    all_out = all_.createoutput()
    all_out.PlugName.set('Output')

    # surface node
    surf = guerilla.Node.create('Surface2', type='RenderGraphNodeShader', parent=rg)
    surf.Mode.set('surface')
    surf.Shader.set('Surface2')
    surf_in = surf.createinput()
    surf_out = surf.createoutput()

    # Trace node
    trace = guerilla.Node.create('Trace', type='RenderGraphNodeSet', parent=rg)
    trace.Membership.set('All,Diffuse,Reflection,Refraction')
    trace_in = trace.createinput()
    trace_in.PlugName.set('set')
    trace_out = trace.createoutput()

    # Lighting node
    light = guerilla.Node.create('Lighting', type='RenderGraphNodeSet', parent=rg)
    light.Membership.set('Lights,Shadows')
    light_in = light.createinput()
    light_in.PlugName.set('set')
    light_out = light.createoutput()

    # all -> surface
    surf_in.Plug.connect(all_out.Plug)

    # surface -> trace
    trace_in.Plug.connect(surf_out.Plug)

    # trace -> light
    light_in.Plug.connect(trace_out.Plug)

    # Layer node
    lay = guerilla.Node.create('Layer', type='RenderGraphNodeRenderLayer',
                               parent=rg)
    lay.Membership.set("layer:Layer")
    lay_vis = lay.createinput()
    lay_vis.PlugName.set('visible')
    lay_matte = lay.createinput()
    lay_matte.PlugName.set('matte')
    lay_out = lay.createoutput()

    # trace -> layer
    lay_vis.Plug.connect(light_out.Plug)

    # Output node
    out = guerilla.Node.create('Output', type='RenderGraphNodeOutput',
                               parent=rg)
    out_in = out.createinput()
    out_in.PlugName.set('Output')

    # layer -> out
    out_in.Plug.connect(lay_out.Plug)

    return rg

Savoir si un RenderGraphNode est connecté à la sortie d’un RenderGraph

La fonction suivante renvoie True si le RenderGraphNode donné est connecté à la sortie de son RenderGraph.

Guerilla connected rendergraph output

def is_connected_to_rendergraph_output(node):
    """Returns True if the given RenderGraphNode is connected
    to an output of the render graph."""

    if getclassname(node) == "RenderGraphNodeOutput":
        # It's connected
        return True

    elif getclassname(node) == "RenderGraphMacroOutput":
        # Give the rendergraph macro itself
        outplugs = node.getparent().getoutputs()

    else:
        # We will test all the output plugs of the node
        outplugs = node.getoutputs()

    for outplug in outplugs:
        for c in outplug.Plug.connections(False, True):
            # We recursively check if the connected nodes flow to the output
            return is_connected_to_rendergraph_output(c.getnode().getparent())

    # We found no connection, let's return
    return False

Color range en SL

Guerilla provide a floatRange() function but nothing to range color. Here is one: Guerilla fourni une fonction floatRange() mais rien pour un range de couleur (aucune colorRange()). En voici une :

color color_range(color in; float inmin, inmax, outmin, outmax, clampIt)
{
    color   r = (in-inmin)/(inmax-inmin);
    return outmin + (outmax-outmin)*(clampIt != 0 ? clamp (r, (0, 0, 0), (1, 1, 1)) : r);
}

World space AABB

Il n’y a aucune façon « officielle » de récupérer la WAABB, mais vous pouvez utiliser la fonction lua interne :

def world_aabb(node):
    """
    Returns:
        guerilla.aabb
    """
    lua_aabb = lua.globals().getselectionworldaabb(guerilla.toLua(node))

    return guerilla.fromLua(lua_aabb)

Scene

Itérer à travers chaque nœud et ses instances

Par défaut, Node.children() n’itère pas à travers les instances. Si A a un enfant B et C a un enfant B qui est une instance pointant sur A|B, Node.children() n’itérera pas à travers C|B et C sera considéré comme n’ayant pas d’enfants :

Guerilla iterate instances

La couleur bleutée de B indique qu’il s’agit d’une instance. Itérer avec Node.children() donnera :

A
A|B
C

L’itérateur suivant utilise l’attribut Node.Instances pour trouver les instances et y itérer :

def walk(node, type_):
    for child in node.children(type_):
        yield child
        for sub_child in walk(child, type_):
            yield sub_child
    for inst_plug in node.Instances.dependencies(source=False, destination=True):
        inst = inst_plug.parent
        yield inst
        for child_inst in walk(inst, type_):
            yield child_inst

Avec un tel itérateur, notre précédente hiérarchie sera traversée de cette façon :

A
A|B
C
C|B

Gardez à l’esprit qu’un tel itérateur peut être très lent si votre scène a énormément d’instances, car il itérera de nombreuses fois à travers les mêmes nœuds.

Récupérer les informations de primitive (face count, vertex count, etc.)

Guerilla dispose d’une fonction non exposé permettant de récupérer des informations de shape :

def get_shape_info(node):

    table = lua.execute('return(_"{}":getshapeinfo())'.format(node.path))

    return {k: table[k] for k in (v for v in table  # lua table to python dict.

Cette fonction renvoie un dictionnaire avec quelques informations utiles :

>>> get_shape_info(node)
{'VarsMemory': 20352L,
 'CurveCount': 0L,
 'VertexCount': 846L,
 'FaceCount': 1688L}

N’étant pas exposées officiellement, gardez à l’esprit que ces informations peuvent être supprimées dans les prochaines versions.

Dernière mise à jour : mar. 02 mars 2021