tips,tricks & geekie stuff

BE KIND, RETIME

tl;dr:  Retime your Camera Node like a PRO!

Working with retimed plates and elements in Nuke oftentimes means that you also need a corresponding retimed camera, so that both plate and camera work back together, and you can still use your fancy projection setups and other camera-dependent stuff.

Now, how on earth do I retime my Camera Node in Nuke? Well, you´re about to find out!

Spoiler: it only takes one click if you use my fresh "b_retime_my_cam" Python Script ;).

1 - A few things to take into account

When we speak about retiming plates, I´m assuming you are doing it how (arguably ) most of the people would: you hook up a Kronos Node to your plate, and use the "frame" option as a "timing" method. This way you can easily create a lookup table mapping input frame numbers into output frame numbers.

Regarding your Camera Node, you are good to use either "regular" Euler cameras or a matrix based one.

2 - Distribute the code

Copy the script and save it as a Python file called "b_retime_my_cam.py" where Nuke can find it. In other words, place it within the Nuke Path file structure. For more info, read my previous posts (or Google it, there´s tons of info covering this topic.)

3 - Edit the menu.py file

Now we have the Python module where Nuke can find it, we´re gonna add the following lines to our fantastic menu.py so that we can effectively use it:

n_menu = nuke.menu('Nuke')
b_menu = n_menu.addMenu('MenuName')
b_menu.addCommand('b_retime_my_cam', 'b_retime_my_cam.main_function()', 'ctrl+a')

Save the menu.py and restart Nuke.

4 - Try it!

You'll notice that there is a brand new Nuke menu in your upper menu bar, listing the new 'b_retime_my_cam' command and its chosen hotkey. Now just select the Kronos Node used to retime your plate and then the Camera Node you want to retime accordingly (strictly in this order) and press 'alt+a '(or any other hotkey of your convenience).

A tiny Nuke Panel like this one should appear:

 

 

 

 

 

 

 

 

 

 

 

 

 

Set the "camera type" (Euler or matrix) and click ok.


 

 

 

 

 

 

 

 

 

 

 

 

 

And voila, you will end up with something like this, which means you have now a new, retimed camera that you can use along with your previously retimed plate.

code
#######################################################################################################################

__author__ = "Boris Martinez Castillo"
__version__ = "1.0.1"
__maintainer__ = "Boris Martinez Castillo"
__email__ = "boris.vfx@outlook.com"

#######################################################################################################################


import nuke
import nukescripts


# CLASS DEFINITIONS

class RetimeCameraPanel(nukescripts.PythonPanel):

    def __init__(self):
        super(RetimeCameraPanel, self).__init__()

        # CREATE KNOBS

        self.name = nuke.Enumeration_Knob("camera type", "camera_type", ["euler", "matrix"])
        self.name.clearFlag(nuke.STARTLINE)
        self.author = nuke.Text_Knob("written by Boris Martinez")

        # ADD KNOBS

        self.addKnob(self.name)


def retime_my_cam(transformation_type):
    """Handle both cases. euler and matrix with given arg.

    Args:
        transformation_type (str): User defined type for transformation.
            Euler or Matrix.

    """
    selection = nuke.selectedNodes()
    nukescripts.clear_selection_recursive()
    classes = ["Camera2", "Kronos"]
    sel_classes = [str(node.Class()) for node in selection]

    colors = {'euler': 536805631,
              'matrix': 4278239231}

    if not sel_classes == classes:
        nuke.message("Please, select first a Kronos and then a Camera Node,in this order")
        return

    for node in selection:
        if node.Class() == "Camera2":
            parent_cam = node
            node["world_matrix"].value()

        elif node.Class() == "Kronos":
            new_cam = nuke.nodes.Camera2(xpos=parent_cam.xpos() + 300,
                                         ypos=parent_cam.ypos())
            new_cam.setInput(0, None)
            kronos_node_name = node.name()
			
            if transformation_type == 'matrix':
                do_matrix_specifics(parent_cam, new_cam, kronos_node_name)

            elif transformation_type == 'euler':
                do_euler_specifics(parent_cam, new_cam, kronos_node_name)

   
            new_cam['tile_color'].setValue(colors[transformation_type])
            new_cam.setName('RETIMED_{}_{}'.format(transformation_type.upper(),
                                                   parent_cam.name()))
            new_cam.setSelected(True)
            custom_backdrop("retimed {} cam".format(transformation_type))


def do_euler_specifics(parent_cam, new_cam, kronos_node_name):
    """Do only euler things.

    Args:
        parent_cam (nuke.Node): Parent cam node.
        new_cam (nuke.Node): New created camera.
        kronos_node_name (str): Kronos node name.

    """
	
    knobs = ['translate', 'rotate', 'scaling', 'uniform_scale', 'skew',
             'pivot', 'focal', 'haperture', 'vaperture', 'near', 'far',
             'win_translate', 'focal_point', 'fstop']

    for knob in knobs:
        expression = '{node}.{knob}({kronos}.timingFrame2)'.format(node=parent_cam.name(),
                                                                   knob=knob,
                                                                   kronos=kronos_node_name)
        new_cam[knob].setExpression(expression)


def do_matrix_specifics(parent_cam, new_cam, kronos_node_name):
    """Do only matrix specific things.

    Args:
        parent_cam (nuke.Node): Parent cam node.
        new_cam (nuke.Node): New created camera.
        kronos_node_name (str): Kronos node name.

    """
    new_cam["useMatrix"].setValue(True)
	
    expression = '{}.world_matrix({}.timingFrame2)'.format(parent_cam.name(),
                                                           kronos_node_name)
    new_cam["matrix"].setExpression(expression)


def custom_backdrop(txt):
    """Create custom backdropNode.

    Args:
        txt (str): Text used as label.

    """
    node = nukescripts.autoBackdrop()
    node['label'].setValue('<center><u><b>' + txt)
    node['note_font_size'].setValue(50)


def main_function():
    """Start up function."""
    panel = RetimeCameraPanel()
    if not panel.showModalDialog():
        print "script aborted"
        return
    else:
        retime_my_cam(panel.name.value())
Read more →

TIME TRAVEL, MY FRIEND

 

tl;dr: Become Marty McFly within Nuke.

Time travel is cool, Nuke is cool, therefore time traveling on Nuke is…you name it.
Anyway, time-traveling sort of means that you can experience time as a solid state dimension which you can transverse back and forth, forth and back, so on a so forth. Would not it be cool to have a slider to go back (or forwards) in time sometimes? I´m pretty sure it would, although it might be a little weird at first.
Now, the little Python-powered Gizmo I got this time to introduce you guys attempts to let you do so, on Nuke. Maybe you are pretty happy with how linear time works in general and will never have to use it at all, but in case you happened to, it may come in handy.
1 – What´s this (really) about.
As you well know, time managing in Nuke is quite flexible, and there is already a lot of tools dealing with time (Retime, TimeWarp, TimeEcho…). In a way, my so-called “B_traail” tool is an “on steroids” version of the TimeEcho Nuke Node, in that it allows you to see in a still image the difference in movement occurred during a given lapse of time. Here´s an example.

On steroids, because that´s pretty much all the TimeEcho Node allows you to do, whereas my little Gizmo creature adds a fair amount of extended functionality. Other than just to “plotting” time, you can also retract both ends of the animation, apply different temporal reduction factors, select multiple merging operations, apply and invert different gradients (linear, quadratic & double-quadratic), time-recolor, and more. 

To better illustrate this, I´ve prepared a simple radial animation with a Roto node, a bunch of Transform nodes and a Merge. Without applying any additional operation, the animation looks like this.

Ok, let´s now bring in a B_traail gizmo and see what we can do with this amazing animation. And just to give you an idea before we get a closer look at the tool, here we have a few examples of what kind of results we could end up having out of the animation by playing a little with it.

2 –  Understanding it.

If you look carefully at these images, you will notice that they represent the animation plotted in one image: 75 frames in only one. Additionally, you may also perceive that there are some alterations in the way those moving dots are shown in the present moment, with respect to how they were: different gradients and merging operations have been applied, both ends of the animation can be retracted to limit the range that we see in one frame, color-over-time has also been applied, and even the temporal density has been dealt with according to different reducing factors. Last but not least, all those parameters can, of course, be animated.

At this point, I´m hoping that the idea has become clear. Probably you have already figured that we could apply this tool to any moving, masked out character to create a trail that we can play with later on, or to a bunch of moving cars to create a long exposure kind of effect. The applications are endless and I can´t wait to see what you guys come up with.
3 – User guide

Let´s now take an in-depth look at the properties bin of the B_traail tool to examine its knobs and see what they do. It´s only a 2 minutes video, don´t be lazy and check it out

4 – Installation guide

Like I said in the beginning, my B_traail Gizmo tool has a Pythonic soul. Behind the scenes, pretty much all the Nuke knobs available on the node´s properties panel trigger a piece of Python code that enables its functionality. More specifically, they call to a number of 34 functions allocated within the context of the b_traail.py module, along 414 lines of code.

This obviously means that you have to save the Python module within the Nuke Plugin Path context for the tool to work. Just download it using the link below and save it where Nuke can find it. NOTE: it´s crucially important to name it “b_traail” exactly. Do not rename the file!.

Great, with that being done, now download the Gizmo file itself and save it so that you can call it from Nuke (ideally in the “Gizmos” folder within the Nuke Plugin Path context).

B_traail.gizmo download link

Now, if you are of a curious type, you´ll probably want to convert the Gizmo into a Group and take a look inside. If you do so before running the tool, what you´ll find will look somehow like this:

No worries, it´s totally normal! Just introduce a frame range, run the tool and its Python code will automatically build-up the setup on the fly. If you take a look inside again, you should be able to see something like this:

Read more →

FASTDROP, MY FRIEND

tl;dr:  save some of your precious time when using the Backdrop Node.

Typically, when you use a Backdrop node to group a bunch of nodes, you want to set up a nice, descriptive label, a font size, and probably also a cool, elegant color. The problem is that usually, those tasks are done after the Backdrop node is created, one after the other, and that gets somewhat repetitive, boring and time-consuming in the long run.

If you want to save the burden and get those three things done at the same time, here I share with you a little Python snippet that will let you deal with your backdrops the easy way, and just at once.

1 - How to use it

So first things first, let´s place the code where it can be reached, create a new Nuke Menu and set up a Hotkey. Let´s see how this works.

Distribute the code

Copy the script and save it as a Python file called "b_custom_bckdrp.py" where Nuke can find it. In other words, place it within the nuke.pluginPath() file structure. For more info, read my previous posts.

Edit the menu.py file

Now we have the Python module where Nuke can find it, we´re gonna add the following lines to our fantastic menu.py so that we can effectively use it:

n_menu = nuke.menu('Nuke')
b_menu = n_menu.addMenu('MenuName')
b_menu.addCommand('b_custom_bckdrp', 'b_custom_bckdrp.main_function()', 'ctrl+a')

Save the menu.py and restart Nuke.

2 - Try it

You'll notice that there is a new menu in your upper menu bar, listing the new 'b_custom_bckdrp' command and its Hotkey. Just select a bunch of nodes and press 'alt+a '(it´s the hotkey we set up earlier, obviously you can change this to any other of your convenience). A Nuke Panel like this one should appear:

 

 

 

 

 

 

 

 

 

 

 

 

 

Set the "note size" and "label" and click ok.


 

 

 

 

 

 

 

 

 

 

 

 

 

Now select a nice, elegant color (or leave it as is).

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Finally, you should end up with something like this :D

code
import nuke
import nukescripts


######################################################################################

__author__ = "Boris Martinez Castillo"
__version__ = "1.0.1"
__maintainer__ = "Boris Martinez Castillo"
__email__ = "boris.vfx@outlook.com"

######################################################################################


def custom_backdrop(txt,fontsize):

    node = nukescripts.autoBackdrop()
    node['label'].setValue('<center><u><b>' + txt)
    #node['bdwidth'].setValue(525)
    prev_sel = nuke.selectedNodes()
    for i in prev_sel:
        i['selected'].setValue(False) 
    node['selected'].setValue(True)
    
    node['note_font_size'].setValue(fontsize)
    nukescripts.color_nodes()


class modalPanel(nukescripts.PythonPanel):

    def __init__(self):
        nukescripts.PythonPanel.__init__(self,"customize_backdrop")


    #CREATE KNOBS

        self.note_size = nuke.Int_Knob("Note Size:")
        self.note_size.clearFlag(nuke.STARTLINE)
        self.frame_display = nuke.String_Knob("Label:")
        self.frame_display.clearFlag(nuke.STARTLINE)
        self.set_note_size_default_value()
        self.author = nuke.Text_Knob("written by Boris Martinez")

    #ADD KNOBS

        for i in (self.note_size , self.frame_display):
            self.addKnob(i)

    #SET KNOB DEFAULT VALUES

    def set_note_size_default_value(self):
        self.note_size.setValue(100)

 
def main_function():
    
    panel = modalPanel()
    if not panel.showModalDialog():
        print "script aborted"
        return
    else:
        fontsize = panel.note_size.getValue()
        note = panel.frame_display.getValue()


    custom_backdrop(note,fontsize)    


if __name__ == "__main__":
    main_function()
Read more →

MUZZLE FLASH, MY FRIEND

 

tl;dr: Locate and sort out your muzzle flashes and other stock footage elements quick and easy.

Face it, digital comping is not only about putting together the pieces of a Transformer, bringing back to life a roaring T-Rex, or giving to a lightsaber the right glowy touch… often it´s also about tackling some tedious, repetitive and highly time-consuming tasks that on top of that usually remain unnoticed and unaccounted for.
Thankfully, there is Python, and here is where being able to write some wise code really comes in handy :).
1 – The challenge
Consider this, you have been given the task of processing a library of stock footage in search of nice muzzle-flashes to use on a given show. Imagine there are around 100-150, 4k clips that you have to play and examine (either in Nuke or Nuke Studio, or Hiero or any other player) to eventually locate the frame or frames where the muzzles flashes are happening on.
 As you probably know, those footage are shot in the dark, so usually you would be watching at noisy black footage up until the moment that, voila, a muzzle flash shows up.

2 – Approach A (be -very- patient)

Ok, once one of those guys appear, you would ideally want to lock down the frame where this is happening. For such a thing, you can use a FrameHold Node in Nuke, a timeline marker in After Effects or just take a note on a notebook, to mention a few options. You would continue the same way up until you would have located all the muzzle flashes.

I leave you to reckon with how long any of these approaches would take…

3 – Aproach B (go -moderately- smart)

Let´s get this straight. What if you could, just by clicking on a button, automatically build a contact sheet displaying all the different muzzle flash elements there are, therefore enabling you to easily check out the different types, camera angles and so on, and then, once you have built that contact-sheet up, go to any of the individual files and extract all the muzzle flashes in that file?

Well, that, along with other fancy add-ons, is exactly what this script does.

4 – How it does it.

Honestly, this is a fairly simple code. Just a few functions, a class definition to help build a nuke Python Panel, and some global variables. In the form of “pseudo code”, here is the thing:

1 – analyze: all (selected) nodes, provided they are Read Nodes, to automatically detect, off of luminance intensities, on which frame or frames a muzzle flash is taking place.

2 – detect: depending on the so-called “build mode” (I´ll go over this later), it either drops a Framehold node each time a muzzle flash is detected or just one for the muzzle flash that has the maximum luminance intensity, thus locking down either all of them, or just the brightest one.

3 – build: with all detected muzzle flashes locked down with a Framehold node, the script then builds a contact sheet with all of them, and then drops a text node connected to each of the files displaying its name on the screen, automatically.

 And as an image is often worth more than a thousand words, this is how it would look like.

5 – How to use it.

Ok, with all that being said, now we should basically place the code where it can be reached, call its main function and then deal with a few options on a Nuke Panel. Let´s see how this works.

1 – distribute the code. As we know, when it comes to Python Scripts, we need to be able to import them in Nuke with the command “import module_name”. We can do this either by defining the PYTHON_PATH environment variable or simply by adding within the init.py file the folder structure where we will place our Gizmos, Plugins, Python Scripts and so on, using the command “nuke.pluginAddPath”. If none of this options sound familiar to you, then just drop the script on to the user´s .nuke folder and hope for the best.

2 – run the tool. Open the Nuke´s Script Editor and type “import module_name” and then type “module_name.main_function()”… Other than this basic way of importing a module and calling a module´s function from Nuke´s Script Editor, you can also implement the code on a Gizmo´s Custom Python Script Button, or create a Nuke menu from where to call your Python Scripts, or even set up a hotkey to easily access to the tool. I´ll leave the implementation part to you.
3 – deal with a few options. Right after running the tool, a pop-up Nuke Panel will appear displaying a few options.
– time range: classic “input/global/custom” pulldown menu. Enables you to analyze your files according to any of these options.

 – build mode: extract will get all the muzzle flashes in all selected clips. Build will get one muzzle flash per clip, exactly that with the maximum brightness.

As a rule of thumb, you probably want to run the tool with the “build” option first and then, once u have built a contact-sheet with a muzzle flash per clip, select a clip from which you would like to extract all the muzzle-flash instances and run the tool in “extract” mode.

important: If you find this usefull, it´s always better to create a Nuke menu from where to call the function.

code

#!/usr/bin/env python

import nuke
import nukescripts

print "importing nuke..."
print "importing nukescripts..."


########################################################################################################################

__author__ = "Boris Martinez Castillo"
__version__ = "1.0.1"
__maintainer__ = "Boris Martinez Castillo"
__email__ = "boris.vfx@outlook.com"

########################################################################################################################

START_FRAME = int(nuke.root()['first_frame'].value())
LAST_FRAME = int(nuke.root()['last_frame'].value())


def get_file_name(f):
    return f['file'].value().split('/')[-1]

def set_name(n):
    txt = nuke.createNode("Text2")
    msg = txt['message'].setValue(n)
    return txt

def content_finder():
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:
        start_frame = i.firstFrame()
        end_frame = i.lastFrame()
        print start_frame
        print end_frame
        i['selected'].setValue('False')  # DE-SELECT CURRENT NODE FROM SELECTION LIST.

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, START_FRAME, LAST_FRAME)  # EXECUTES CURVE TOOL

        if type(get_stop_frame(cv)) == list:

            for e in get_stop_frame(cv):
                fh = nuke.createNode("FrameHold")
                fh.setInput(0, cv)
                fh['first_frame'].setValue(int(e))
                label = set_name(get_file_name(i))
                txt_sel_list.append(label.name())
                read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print "txt_sel_list: ", txt_sel_list
            return txt_sel_list

def content_finder_input():
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:
        start_frame = i.firstFrame()
        end_frame = i.lastFrame()
        print start_frame
        print end_frame
        i['selected'].setValue('False')  # DE-SELECT CURRENT NODE FROM SELECTION LIST.

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, start_frame, end_frame)  # EXECUTES CURVE TOOL

        if type(get_stop_frame(cv)) == list:

            for e in get_stop_frame(cv):
                fh = nuke.createNode("FrameHold")
                fh.setInput(0, cv)
                fh['first_frame'].setValue(int(e))
                label = set_name(get_file_name(i))
                txt_sel_list.append(label.name())
                read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print "txt_sel_list: ", txt_sel_list
            return txt_sel_list

def content_finder_custom(sf,lf):
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:
        start_frame = i.firstFrame()
        end_frame = i.lastFrame()
        print start_frame
        print end_frame
        i['selected'].setValue('False')  #  DE-SELECT CURRENT NODE FROM SELECTION LIST

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, sf, lf)  #  EXECUTES CURVE TOOL

        if type(get_stop_frame(cv)) == list:

            for e in get_stop_frame(cv):
                fh = nuke.createNode("FrameHold")
                fh.setInput(0, cv)
                fh['first_frame'].setValue(int(e))
                label = set_name(get_file_name(i))
                txt_sel_list.append(label.name())
                read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print "txt_sel_list: ", txt_sel_list
            return txt_sel_list

def content_finder_int():
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:

        i['selected'].setValue('False')  # DE-SELECT CURRENT NODE FROM SELECTION LIST

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, START_FRAME, LAST_FRAME)  #  EXECUTES CURVE TOOL

        if type(get_stop_frame_int(cv)) == int:

            fh = nuke.createNode("FrameHold")
            fh['first_frame'].setValue(get_stop_frame_int(cv))
            label = set_name(get_file_name(i))
            txt_sel_list.append(label.name())
            read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print txt_sel_list
            return txt_sel_list

def content_finder_int_input():
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:
        start_frame = i.firstFrame()
        end_frame = i.lastFrame()
        print start_frame
        print end_frame
        i['selected'].setValue('False')  # DE-SELECT CURRENT NODE FROM SELECTION LIST

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, start_frame, end_frame)  #  EXECUTES CURVE TOOL

        if type(get_stop_frame_int(cv)) == int:

            fh = nuke.createNode("FrameHold")
            fh['first_frame'].setValue(get_stop_frame_int(cv))
            label = set_name(get_file_name(i))
            txt_sel_list.append(label.name())
            read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print txt_sel_list
            return txt_sel_list

def content_finder_int_custom(sf,lf):
    SEL = nuke.selectedNodes()  # HOLD ALL SELECTED NODES
    fh_sel_list = []
    txt_sel_list = []
    counter = 0

    for i in SEL:
        start_frame = i.firstFrame()
        end_frame = i.lastFrame()
        print start_frame
        print end_frame
        i['selected'].setValue('False')  # DE-SELECT CURRENT NODE FROM SELECTION LIST

        cv = nuke.createNode("CurveTool")
        nuke.execute(cv, sf, lf)  # EXECUTES CURVE TOOL
        print
        if type(get_stop_frame_int(cv)) == int:

            fh = nuke.createNode("FrameHold")
            fh['first_frame'].setValue(get_stop_frame_int(cv))
            label = set_name(get_file_name(i))
            txt_sel_list.append(label.name())
            read_recolor(nuke.toNode(i.name()), 112656639)
            counter += 1
            print counter
        else:
            read_recolor(nuke.toNode(i.name()), 3238002943)
            counter += 1
        if counter == len(SEL):
            print txt_sel_list
            return txt_sel_list

def attach_contact_sheet(l):

    print "attach_contact_sheet list: ", l
    all_nodes = len(l)
   	
    [nuke.toNode(i)['selected'].setValue('True') for i in l]
    contact_sheet = nuke.createNode("ContactSheet")
    columns = int(contact_sheet["columns"].getValue())
    rows = (all_nodes / columns) + 1
    contact_sheet["rows"].setValue(rows)
    
	
def read_recolor(l,c):
    l['tile_color'].setValue(c)
    return

def get_other_muzzles(l,f,a):
    for i in l:
        if i > f:
            a.append(i)
    return a

def get_frames(anim,value,li):
    frame = 0
    for key in anim.keys():
        x_value = key.x
        y_value = key.y
        if y_value == value:
            frame = x_value
            li.append(frame)
            break
    return li

def get_stop_frame(node):
    anim_curve_x = node['intensitydata'].animation(0)
    y_list = []
    stop_frame = 0

    for key in anim_curve_x.keys():
        x_value = key.x
        y_value = key.y
        y_list.append(y_value)


    y_max_value = max(y_list) # CALCULATE MAX VALUE
    other_muzzles = []
    other_stop_frames = []

    print "max value found: ", y_max_value

    om = get_other_muzzles(y_list,0.05,other_muzzles)
    print "other_muzzles: ",om
    for i in om:
        get_frames(anim_curve_x,i,other_stop_frames)

    print "other_stop_frames: ", other_stop_frames

    if y_max_value < 0.05:
        return None

    for key in anim_curve_x.keys():
        x_value = key.x
        y_value = key.y
        if y_value == y_max_value:
            stop_frame = x_value
            break

    return other_stop_frames

def get_stop_frame_int(node):
    anim_curve_x = node['intensitydata'].animation(0)
    y_list = []
    stop_frame = 0

    for key in anim_curve_x.keys():
        x_value = key.x
        y_value = key.y
        y_list.append(y_value)


    y_max_value = max(y_list)  # CALCULATE MAX VALUE
    other_muzzles = []
    other_stop_frames = []

    print "max value found: ", y_max_value

    om = get_other_muzzles(y_list, 0.05, other_muzzles)
    print "other_muzzles: ", om
    for i in om:
        get_frames(anim_curve_x, i, other_stop_frames)

    print "other_stop_frames: ", other_stop_frames

    if y_max_value < 0.05:
        return None

    for key in anim_curve_x.keys():
        x_value = key.x
        y_value = key.y
        if y_value == y_max_value:
            stop_frame = x_value
            break

    return int(stop_frame)

def main_function():
    panel = modalPanel()
    if not panel.showModalDialog():
        print "script aborted"
        return
    else:
        print "script run"
        if panel.giveFrameRangeValue() == "global" and panel.analysis_mode.value() == "extract" :
            attach_contact_sheet(content_finder())
            return
        elif panel.giveFrameRangeValue() == "input" and panel.analysis_mode.value() == "extract":
            attach_contact_sheet(content_finder_input())
            return
        elif panel.giveFrameRangeValue() == "custom" and panel.analysis_mode.value() == "extract":
            range = panel.frame_display.value()
            start_frame = int(range.split("-")[0])
            end_frame = int(range.split("-")[-1])
            print start_frame, end_frame
            attach_contact_sheet(content_finder_custom(start_frame,end_frame))
            return
        elif  panel.giveFrameRangeValue() == "global" and panel.analysis_mode.value() == "contact sheet":
            attach_contact_sheet(content_finder_int())
            return
        elif panel.giveFrameRangeValue() == "input" and panel.analysis_mode.value() == "contact sheet":
            attach_contact_sheet(content_finder_int_input())
            return
        elif panel.giveFrameRangeValue() == "custom" and panel.analysis_mode.value() == "contact sheet":
            range = panel.frame_display.value()
            start_frame = int(range.split("-")[0])
            end_frame = int(range.split("-")[-1])
            print start_frame, end_frame
            attach_contact_sheet(content_finder_int_custom(start_frame, end_frame))
            return
        else:
            print "pass"
            return

#CREATE A PYTHON PANEL FOR DEALING WITH TIME RANGES

class modalPanel(nukescripts.PythonPanel):

    def __init__(self):
        nukescripts.PythonPanel.__init__(self,"get my muzzle")
        #CREATE KNOBS
        self.frame_range = nuke.Enumeration_Knob('fRange','frame range', ['global','input','custom'])
        self.analysis_mode = nuke.Enumeration_Knob('mode','build mode  ', ['contact sheet','extract'])
        self.frame_display = nuke.String_Knob("")
        self.frame_display.clearFlag(nuke.STARTLINE)
        self.author = nuke.Text_Knob("written by Boris Martinez")
        #ADD KNOBS
        for i in (self.frame_range,self.frame_display,self.analysis_mode,self.author):
            self.addKnob(i)
        #SET KNOB DEFAULT VALUES
        self.get_frame_range()

    def giveFrameRangeValue(self):
        return self.frame_range.value()

    def get_frame_range(self):
        if self.giveFrameRangeValue() == "global":
            first_frame = nuke.root().firstFrame()
            last_frame = nuke.root().lastFrame()
            txt = str(int(first_frame)) + '-' + str(int(last_frame))
            self.frame_display.setValue(txt)
        elif self.giveFrameRangeValue() == "input":
            print "here should come the read frame range"
            node = nuke.selectedNode()
            first_frame = node.firstFrame()
            last_frame = node.lastFrame()
            txt = str(int(first_frame)) + '-' + str(int(last_frame))
            self.frame_display.setValue(txt)
        elif self.giveFrameRangeValue() == "custom":
            self.frame_display.setValue("")
            print "here the user decides"

    def knobChanged(self,knob):
        if knob.name() == "fRange":
            self.get_frame_range()


if __name__ == "__main__":
    main_function()


Read more →

Batch Gizmo 2 Grizmo Creator

tl;dr: Batch convert your favourite Nuke Gizmos into “Grizmos”.

Hi folks,
 
So here I have a new little Python script for those who find a little boring converting Gizmos into “Grizmos” manually. Just in case, I´ll remind you that one of the best ways to make sure all your fancy Gizmos are going to render in the farm, or that a user that does not have a particular Gizmo can still open a comp that has it and work on it, is simply converting that Gizmo into a Group, thus getting the so-called  “Grizmo” (as Hugo Lévéille would call´em).
What this script does, in its first version, is basically creating a Grizmo per each Gizmo found in a passed directory. AKA a batch Gizmo-to-Group converter, or a batch Grizmo Creator… And only for Windows users, at the moment.
How to use it?
1 – Store and name the Python script in a folder that Nuke is going to look at when it comes to importing Python scripts. Try the command “print nuke.pluginPath()” in the script editor if you are not sure where your Nuke installation is looking for Plugins, Python Scripts and such.
2 – Import the script you have just saved from Nuke´s script editor like “import module_name“.
3 – Call the main()  function from the imported script like “module_name.main()“.
4 – If everything goes ok, you should immediately see a pop-up window asking for you to select the “Gizmos Path” directory. Navigate to the folder where you store all your fancy Gizmos and click ok.
5 – Now you should have as many Grizmos as Gizmos you had in the former directory, with the “_grizmed” extension added to the original name. For instance:
    – Cryptomatte.gizmo (original Gizmo)
    – Cryptomatte_grizmed.gizmo (Grizmed Gizmo)
6 – Enjoy your brand new fancy Grizmos :D

important: If you find this usefull, it´s always better to create a Nuke menu from where to call the function.

snippet
import nuke
import os
import nukescripts

def transform_line(line):
    """ basic character replacement function.
    :param line: a string to be passed to perform character replacement.
    :return: the line mutated with the new replaced character.
    """
    word_replacements = {'Gizmo': 'Group'}
    for key, value in word_replacements.iteritems():
        line = line.replace(key, value)
    return line


def gizmo_to_grizmo(i):
    """ this function creates a "grouped" .gizmo file.
    :param i: input file to open
    :return: None
    """
    o = str.split(i,".")[0] + "_grizmed" + "." + str.split(i,".")[-1]
    with open(o, "w") as output_file, open(i) as input_file:
        for line in input_file:
            output_file.write(transform_line(line))
    return None


def grizmo_my_folder(dir):
    """ this fuction parses a directory and applies the gizmo_to_grizmo to its files, if convenient.
       :param dir: directory to be parsed.
       :return: None
       """
    for subdir, dirs, files in os.walk(dir):
        for file in files:
            # print os.path.join(subdir, file)
            filepath = subdir + os.sep + file
            if "grizmed" in filepath:
                print "already grizmed"
                return
            elif filepath.endswith(".gizmo") and "grizmed" not in filepath:
                gizmo_to_grizmo(filepath)
                print "gizmos grizmed succesfully"
            else:
                print "no valid file found, bruh"
                return
            

class Panel(nukescripts.PythonPanel):
    def __init__(self, name):
        super(Panel, self).__init__(name)
        # self.setMinimumSize(500,500)
        self.path = nuke.File_Knob('gizmos_path', 'Gizmos Path')

        self.addKnob(self.path)

    def show_modal_dialog(self):
        nukescripts.PythonPanel.showModalDialog(self)


def main():
    """
    wrapper function. From here we open the Nuke Panel to find the Gizmo Directory and launch the rest of the program
    :return: None
    """
    p = Panel("Grizmo Maker")
    p.show_modal_dialog()
    path = p.path.getValue()
    grizmo_my_folder(path)

    if path:
        panel = nuke.message("Grizmification completed")
        print "Grizmification completed"
    else:
        panel = nuke.message("Grizmification aborted")
        print "Grizmification aborted"
    return


if __name__ == '__main__':
    main()
Read more →

Build a camera from EXR metadata

tl;dr: Leveraging the power of the EXR metadata to create an animated Camera Node in Nuke.

When it comes to designing a comp template intended to be used along an entire show as a base-comp (a comp from where to start working on different shots later on), being able to make it the most streamlined possible is of paramount importance. Here is where metadata comes into place.
Like you may already know, an EXR file can store metadata values that can be accessed and used in Nuke to dynamically drive the parameters of virtually any node. Hence, streamlining a so called base-comp implies automatizating certain tasks and ultimately saving time (and money), avoiding human errors.
This being said, here I share with you an example of such a thing: a Python script which is able to create a Camera Node from the Arnold Render specific metadata stored in a EXR file, thus making it unnecesary to export alembic or fbx caches and then saving us one step in the pipeline.

important: for making this work, import the script and call the create_cam() function.

snippet
# local application/library specific imports
import nukescripts
import nuke


def bake_arnold_exr_cam(name, eye):

    try:
        node = nuke.selectedNode()
    except ValueError:
        nuke.message("select a node")
        return

    if node.Class() != "Read":
        nuke.message("you should select an EXR file containing metadata")
        return
    else:
        cam = nuke.createNode('Camera2')
        cam['useMatrix'].setValue(False)
        cam['rot_order'].setValue('XYZ')
        # cam.setName("EXR_right_camera_" + node.name())
        cam.setName("EXR_" + name + "_camera_" + node.name())

        for k in ('focal', 'haperture', 'vaperture', 'translate', 'rotate'):
            cam[k].setAnimated()

        for frame in range(node.firstFrame(), (node.lastFrame()) + 1):
            haperture = node.metadata('exr/g' + eye + '_cameraApertureMmH', frame)
            vaperture = node.metadata('exr/g' + eye + '_cameraApertureMmV', frame)
            focal = node.metadata('exr/g' + eye + '_focalLength', frame)

            transX = node.metadata('exr/g' + eye + '_cameraTranslationX', frame)
            transY = node.metadata('exr/g' + eye + '_cameraTranslationY', frame)
            transZ = node.metadata('exr/g' + eye + '_cameraTranslationZ', frame)

            rotX = node.metadata('exr/g' + eye + '_cameraRotationX', frame)
            rotY = node.metadata('exr/g' + eye + '_cameraRotationY', frame)
            rotZ = node.metadata('exr/g' + eye + '_cameraRotationZ', frame)

            cam['focal'].setValueAt(float(focal), frame)
            cam['haperture'].setValueAt(float(haperture), frame)
            cam['vaperture'].setValueAt(float(vaperture), frame)

            cam['translate'].setValueAt(float(transX), frame, 0)
            cam['translate'].setValueAt(float(transY), frame, 1)
            cam['translate'].setValueAt(float(transZ), frame, 2)
            cam['rotate'].setValueAt(float(rotX), frame, 0)
            cam['rotate'].setValueAt(float(rotY), frame, 1)
            cam['rotate'].setValueAt(float(rotZ), frame, 2)

        return nuke.message( name + " " + "camera" + " " + "created")



class ModalPanel(nukescripts.PythonPanel):
    def __init__(self):
        nukescripts.PythonPanel.__init__(self, title="BAKE YOUR CAM")
        self.menu = nuke.Enumeration_Knob('eye', 'eye:', ['left', 'right'])
        self.addKnob(self.menu)

    def getValue(self):
        return self.menu.getValue()

    def getName(self, n):
        return self.menu.value()


def create_cam():
    panel_instance = ModalPanel()
    if panel_instance.showModalDialog():

        value = int(panel_instance.getValue())
        cam_created = panel_instance.getName(value)

        if value == 0:
            bake_arnold_exr_cam("left", "_L")
            # ok_panel = nuke.message(cam_created + " " + 'camera' + " " + "created")
        elif value == 1:
            bake_arnold_exr_cam("right", "_R")
            # ok_panel = nuke.message(cam_created + " " + 'camera' + " " + "created")   #ok_panel.show()
    else:
        pass
        print("next time")


		
if __name__ == '__main__':
    create_cam()


Read more →

FrameHold to current frame

tl;dr:A useful tweak to the “FrameHold” node that set by default its “frame” knob to the current frame in the timeline.

By the time I´m writing this, probably everyone has its own version of such a simple, yet day-to-day usefull tweak of a tool. Nonetheless, a couple of weeks ago a college of mine asked me to modify the “FrameHold” node so that, whenever we create one, it gets its “first_frame” knob automatically set to the frame in the timeline it is created. This imply some people could still benefit from reading this :)
My implementation of such a thing consists of a mini function that is passed to an “addOnUserCreate” callback as callable argument whenever a “FrameHold” node is created.

important: make sure to copy this code somewhere in your meny.py file to make it work. Enjoy!

snippet
.__autor__ Boris Martinez 

# function to be passed later on to a addOnUserCreate callback.
def frame_hold_to_frame():
    node = nuke.thisNode()
    curr_frame = nuke.frame()
    frame = node['first_frame'].setValue(curr_frame)
	return curr_frame
# addOnUserCreate callback.
nuke.addOnUserCreate(frame_hold_to_frame,nodeClass ="FrameHold")
Read more →

“SmartPos” knob

tl;dr: A useful custom knob that enables the user to to place objects in 3D while working in screen space.

I called “SmartPost” to a custom knob that enables the user to color-pick positional values from a World Point Position Pass (WPP) and then use those values to drive the XYZ coordinates of a XYZ_Knob existing it tools such as the Axis, the TransformGeo, the Sphere, the Card or the Cube node. By doing so, basically one is able to position geometry, cards, axis and such in worldspace based on color values.
This knob can therefore come in handy when working in animation projects (where the WPP is typically rendered by default), allowing us to place objects in 3D while working in screen space, ultimately saving a great deal of time and guessing work.

important:  make sure to copy this code somewhere in your meny.py file to make it work.

snippet
.__author__ Boris Martinez

# assign nuke.createNode() function to a variable for later use.
nuke_create_node = nuke.createNode

def create_my_nodes(node, knobs = "", inpanel = True):
    """
    this function calls nuke_create_node and add a tab and a color picker knob based on the class
    of the node created.
    @param node: Node class.
    @param knobs: Optional string containing a TCL list of name value pairs (like "size 50 quality 19")
    @param inpanel:  Optional boolean to open the control bin (default is True; only applies when the GUI is running).
    @return: result
    """
    result = nuke_create_node(node, knobs, inpanel) # call to nuke_create_node

    if node == "Sphere" or node == "TransformGeo" or node == "Cube" or node == "Card2" or node == "Cylinder" or node \
            == "Axis2":
        tab = nuke.Tab_Knob('SmartPos', 'SmartPos')
        col = nuke.Color_Knob('PickPos')
        result.addKnob(tab)
        result.addKnob(col)

    return result

# overwrite nuke.createNode
nuke.createNode = create_my_nodes

# KnobChanged callable function

def smart_post_expression():
    """
    This is the function to be called as the first argument for the AddKnobChanged callback later on.
    Sets its context to nuke.thisNode() and picks up the changing knob by the nuke.thisKnob() command.
    @return: None
    """
    n = nuke.thisNode()
    k = nuke.thisKnob()
    if k.name() == "PickPos":
        n['translate'].setExpression('PickPos.r', 0)
        n['translate'].setExpression('PickPos.g', 1)
        n['translate'].setExpression('PickPos.b', 2)
    return None

# adding callbacks to certain node classes.

nuke.addKnobChanged(smart_post_expression, nodeClass="TransformGeo")
nuke.addKnobChanged(smart_post_expression, nodeClass="Sphere")
nuke.addKnobChanged(smart_post_expression, nodeClass="Cube")
nuke.addKnobChanged(smart_post_expression, nodeClass="Card2")
nuke.addKnobChanged(smart_post_expression, nodeClass="Cylinder")
nuke.addKnobChanged(smart_post_expression, nodeClass="Axis2")


 

As a side note, I´d like to note that I don´t think it´s ideal the fact that the new “SmartPos” tab created appears first in the properties bin whenever we create a node that´s intended to have it. I´d like to see the main tab first, but so far I didn´t find the way. Any suggestion would be much appreciated. Thanks for reading!
 
Read more →

3 simple Python snippets that will make your life (a bit) easier

tl;dr: some usefull Python snippets for Nuke.

So I’m finally applying some Python knowledge I’ve been learning lately to actually usefull stuff. Cool!! I will celebrate it by sharing some very, super simple Python snippets that will enable you to work (slightly) faster within Nuke.
Disclaimer: I’m fairly new to Python so you can expect finding not very elegant, unpythonic code. Sorry for that!
snippet #1
This tiny piece of code enables you to automatically create a new ReadNode reading the filepath from a selected WriteNode. Mapping this code to a hotkey makes it a really handy action to use. So let’s see the actual code:
 
.__author__ Boris Martinez

import nuke, re, math, os

def AutoReadFromWrite():

    try:
        n = nuke.selectedNode()
		
    except:
    
        m = nuke.message('select a WriteNode,dude')
        m.show()
	else:
		if n.Class() == 'Write':
			#fetching data from the write node
            p = n.knob('file').getValue()
			fstf = n.knob('first').getValue()
			lstf = n;knob('last').getValue()
			colorspace = str(i.knob('colorspace').getValue()
			#fetching data from the write node
            r = nuke.createNode('Read')
            r.knob('file').setValue(p)
			r.knob('first').setValue(int(firstFrame))
			r.knob('last').setValue(int(lastFrame))
			r.knob('origfirst').setValue(int(firstFrame))
			r.knob('origlast').setValue(int(lastFrame))
			r.knob('colorspace').setValue(colorspace)
		else:
			m = nuke.message("Select a Write node, dude!")
		
		
 AutoReadFromWrite()
snippet #2
This ones takes advantage of some of the concepts from the AutoReadFromWrite function to enable the user to open in the explorer the folder that the filepath knob of a selected ReadNode is pointing to. 
 
.__author__ Boris Martinez

import nuke,os

def AutoExploreFromRead():
    
    try:
        n = nuke.selectedNode()
        f = n.knob("file").getValue()
        p = os.path.dirname(f)
        print "path is %s" %p
        os.startfile(p)
    except:
        p = nuke.message("no file selected")
        p.show()
        print "no file selected"

AutoExploreFromRead()
snippet #3
Here we have a little trickier code comprising concepts we did not see in the previous ones. Stuff like iterations, classes and GUI creation. Functionality wise, it’s really simple! What it does is just creating a randomized animation curve in selected lights. Useful if you want to create some randomized animation for lights in the scene (it affects specifically the intensity value of selected lights).
 
.__author__ Boris Martinez

import nuke
import random
import nukescripts


def lightRandomizer(m,i,o):
    try:
        nodes = nuke.selectedNodes()
        variation = None
        for light in nodes:
            for e in range(i,o):
                variation = random.randrange(1,5)
                if light.Class() == "Light2":

                    light['intensity'].setAnimated()
                    light['intensity'].setValueAt(variation * m,e)
    except:
        ep = nuke.message("No nodes selected")
        ep.show()

def lightRandExpression():
    nodes = nuke.selectedNodes()
    variation = None
    for light in nodes:
        variation = random.randrange(1,5)
        if light.Class() == "Light2":
            light['intensity'].setExpression("random(frame)")
            light['intensity'].setAnimated()

#creating the panel for introducing a multiplicator and launching the lightRandomizer function

class modalPanel(nukescripts.PythonPanel):

    def __init__(self):
        nukescripts.PythonPanel.__init__(self,"multiplier")
        self.m = nuke.Double_Knob("intensity multiplier", "intensity multiplier: ")
        self.addKnob( self.m )
        self.fRange = nuke.String_Knob('fRange', 'Animation Range', '%s-%s' % (nuke.root().firstFrame(), nuke.root().lastFrame()))
        self.addKnob(self.fRange)

    def showModalDialog( self ):
        nukescripts.PythonPanel.showModalDialog(self)

    def giveMultValue(self):
        return  self.m.getValue()

    def giveFrameRangeValue(self):
        return self.fRange.getValue()

#wrapper function

def lightRandmWrapper():
    panel = modalPanel() # an instance from the modalPanel Class
    show = panel.showModalDialog()
    multiplier = panel.giveMultValue()
    frameRange = panel.giveFrameRangeValue()
    frameRangeSplitted = frameRange.split("-")
    ini = int(frameRangeSplitted[0])
    fin = int(frameRangeSplitted[-1])

    #print "frameRange is: ", frameRange
    #print type(frameRange)

    lightRandomizer(multiplier,ini,fin)
    #p = nuke.message (" Boris says: lights randomized, cool!")
    #p.show()

lightRandmWrapper()
#lightRandExpression

So, for the time being, this is all folks. Feel free to copy this snippets for your personal use in Nuke. For snippets #1 & #2 I highly recommend mapping them to a hotkey. If you don’t know how to do so, checking Fxphd’s “Python for Nuke & Gizmology” course by Victor Perez would be really helpfull. For other general Python questions, I strongly recommend Mark Lutz’s “Programming Python” book.
 
Read more →