tips,tricks & geekie stuff

Gizmo Snatch Tool (and how to install your Gizmos wisely)

tl;dr:  Snatch loads of Gizmos and Groups and install them like a PRO!

I don't know about you, but over the years I have curated a generous collection of Gizmos that I like having in my toolkit no matter where I go to do my comping thing. On the same note, whenever I am working on x or y company and I come across some new gems that I think would be a nice addition to my collection, I tend to snatch 'em, assuming of course that I am not infringing any copyright law and so on...

With that said, what this tool is going to allow you to do, is to essentially select as many Gizmos and Groups as you want to snatch, and save them all quickly in a folder of your choice, thus avoiding the "export as Gizmo" slow and tedious workflow, which apart from other more esoteric options, would seem like the obvious way to do it (correct me if I am wrong, please!)

2 - Distribute the code

As usual, copy the script and save it as a Python file called "b_gizmo_snatcher.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.)

code
Download File

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_gizmo_snatcher', 'b_gizmo_snatcher.main_function()', 'any hotkey of your choice')


Save the menu.py and restart Nuke.

Alternatively, you could load the script in the Script Editor and run the "main_function()" at the end, but that's a little too nerdy, don't do that.

4 - Try it!

You'll notice that there is a brand new Nuke menu in your upper menu bar, listing the new 'b_gizmo_snatcher' command and its chosen hotkey. Now just select as many Gizmos and Groups as you want to snatch and run the tool.

A tiny Nuke Panel like this one should appear:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Select your "snatch" folder and click ok.

And voila, navigate to the chosen folder and you should find there all the Gizmos and Groups you've previously selected. By the way, as a side note, when snatching actual Gizmos and not Grizmos or Groups, the tool is first going to convert the Gizmo into a Group and then export the Group to the folder, adding the suffix "_toGroup" to the original Gizmo's name... just so you know!

bonus snippet!!

Now, after having snatched, created, and downloaded your favorite Gizmos, it's time to deploy them within your little Nuke path. While you can just drop them inside the .nuke folder and use the command "update" inside the "Other/All plugins" menu on Nuke's Toolbar, this is probably the laziest and less organized way to go.

Alternatively, as you probably already know, you can create menus and submenus on Nuke's Toolbar to add your Gizmos, using these simple commands:

nuke.menu("Nodes").addMenu("MyMenu", icon = "MyMenuLogo.png")
toolbar.addCommand("MyMenu/MySubMenu/ToolName", "nuke.createNode("ToolName")")

Although there's nothing wrong with this option, just think of having to write one line of code for each of the Gizmos you wanted to add to your menus, in addition to having to create and name those menus by hand. Yuck!

Now, one smart way to do this, would be to have all these gizmos distributed in folders and subfolders named as we want our Nuke menus and submenus to be named, and somehow write some hacky snippet that will automatically create the menus based off of these folders, name them accordingly, and list all the contained tools, as well as adding the menu icon again, automatically. For this to work (in this implementation I am sharing), we have to ensure 4 things:

1 - that the folders will be named according to this syntax: <gizmos_JohnDoe>. Namely, the word "gizmo" an underscore "_" and any name we wanted, in this case, "JohnDoe".

2 - that the icon to be used will be named exactly like the name after the underscore, in this case, "JohnDoe" and it will be a PNG image. I.e: "JohnDoe.png"

3 - that the folder will be on the same root where the menu.py is.

4 - that there will not be more than two nested levels in the gizmo folders, meaning there can be only subfolders inside the gizmo folders (to create subcategories), but not sub-subfolders.

With that out of the way, just copy this little snippet inside your menu.py, save it and you should be good to go!

code
################# Import a lot of tools 

# Import Gizmos

toolbar = nuke.menu("Nodes");
gizmodir = os.path.dirname(__file__)
print "GIZMODIR: ", gizmodir

def deploy_gizmos():

    for gd in os.listdir(gizmodir):
        
        upper_path = os.path.join(gizmodir,gd)
            
        if gd.startswith('gizmos_') and os.path.isdir(os.path.join(gizmodir,gd)):
            nuke.pluginAddPath('./'+gd)
            
            for sub_gd in os.listdir(os.path.join(gizmodir,gd)):
                if sub_gd.startswith('gizmos_') and os.path.isdir(os.path.join(gizmodir,gd,sub_gd)):
                    lower_path = os.path.join(gizmodir,gd,sub_gd)
                    nuke.pluginAddPath('./' + sub_gd)

    for gd in sorted(os.listdir(gizmodir), key=lambda f: f.lower()):

        if gd.startswith('gizmos_') and os.path.isdir(os.path.join(gizmodir,gd)):
            print gd[7:], gd[7:]+'.png'
            menu = toolbar.addMenu(gd[7:], gd[7:]+'.png')
            nuke.pluginAddPath('./'+gd)
            
            for gizmo in sorted(os.listdir(os.path.join(gizmodir,gd)), key=lambda f: f.lower()):
                if gizmo.endswith('.gizmo'):
                    menu.addCommand(gizmo[:-6], 'nuke.createNode("'+gizmo[:-6]+'")')
                    
            for sub_gd in os.listdir(os.path.join(gizmodir,gd)): 
                if sub_gd.startswith('gizmos_') and os.path.isdir(os.path.join(gizmodir,gd,sub_gd)):
                                                
                    super_menu = gd[7:]
                    sub_menu = sub_gd[7:]        
                    combined_menu = super_menu + "/" + sub_menu
                    to_plugin_path = "'./"+sub_gd +"'"
                    to_plugin_path = os.path.join(gizmodir,gd,sub_gd)
                        
                    menu = toolbar.addMenu(combined_menu, sub_gd[7:]+'.png')
                    nuke.pluginAddPath(to_plugin_path)    
                    
                    for gizmo in sorted(os.listdir(os.path.join(gizmodir,gd,sub_gd)), key=lambda f: f.lower()):
                        if gizmo.endswith('.gizmo'):
                            menu.addCommand(gizmo[:-6], 'nuke.createNode("'+gizmo[:-6]+'")')
                            
deploy_gizmos()
                

 

Read more →

B_deep_toolset: a Deep Comp Survival Kit

tl;dr: a very useful deep compositing toolkit you probably want to use.

Working with Deep is super cool, but can also be very, very slow. Luckily, with the inestimable help of Python,  there are different strategies we can use to leverage the power of Deep Comping without compromising our script responsiveness and our own productivity. Here’s how:
installation guide

So first things first, let´s place the Python script where it can be reached so that we can actually use the tools.

Distribute the code

Download the script from the link below and save it as a Python file called "b_deeptoolset.py" where Nuke can find it. In other words, place it within the Nuke Plugin Path. For more info, read my previous posts or Google it, there are tons of tutorials on how to do this.

code
Download File

Additionally (although very much encouraged) download and install the "dDot" Python module available in Nukepedia. Please note that I have included a few (but not all) unmodified functions from this module inside my "b_deeptoolset.py" so that you can still use the ''Autocomp'' feature even if you don´t have installed "dDot".  Needless to say, All dDot's related functionality credit goes to its creator Daniel Bartha.

Edit the menu.py file

Now we have the "b_deeptoolset.py" Python module where Nuke can find it, we´re gonna import it by adding the following line to our menu.py :

import b_deeptooset


Save the menu.py and restart Nuke.

2 - Try it

You'll notice that there are a couple of new menus on your menu bar, namely: "b_deeptoolset" and "dDot". If this is the case, congratulations, we are ready to start using the Toolkit. Otherwise please make sure the Python script is saved where Nuke can find it, and that is named with exactly the same name you are using to import it from your ''menu.py''.

DEEP HOLDOUTS
One of the coolest things about Deep is that it allows for volumetric out-holding between our CG elements, as we all know. The problem is that the amount of data needed to achieve this oftentimes is massive, rendering our scripts very sluggish.
The solution is to create and pre-comp a set of mattes to holdout the CG elements with each other in 2d, thus allowing for regular 2d merge as though they were combined in Deep.

user guide

  • 1 - Make sure each CG element and its Deep Data is connected to the corresponding inputs of a DeepRecolor node.
  • 2 - Make sure each DeepRecolor node has the target input alpha checked, and channels set to all.
  • 3 - Make sure the regular 2d element is on the left-hand side and the DeepRead on the right. Also, it's gonna look better with a dot creating a right angle between the DeepRecolor and the regular 2d element. Like so:

  • 4 - To make it Autocomp ready, please drop a couple of dDot nodes, as shown above, named according to the following convention. To drop the dDots, either use the hotkey "Shift+." or go to the "dDot" menu and run the "dDotParent" tool. A little Nuke Panel will pop up for you to input the name. Once done that, just click OK and a new dDot node will be created.
  •     4.1 - For the regular 2d input name, please use any alphanumeric string. It may be as long as you need and could use camel case in case you want to include several words. Please avoid empty spaces or underscores. For instance, all these would be valid names:
  • "road",
  • "2dRoadCam1"
  • "whatEverYouWantAsLongAsItIsInOneWordWithNoSpacesOrUnderScores1212"
  •     4.2 - For the deep input, just add a "_deep" trailing to the former name. For instance:
  • "road_deep".
  •     4.3 - Please note that if you don't drop dDots there, everything will still work fine, just without the Autocomp feature. And by the way, the dDots are just regular Dot Nodes with an additional hidden Knob that allows for smart linking and that type of stuff. They won't cause any trouble otherwise!
  • 5 - Voila, select all your DeepRecolor (and only DeepRecolor ) nodes and click on the "create_holdouts" button. If everything goes fine, this is what you'll get:

A full set of holdouts to stencil your CG elements with each other :)
6 - Now you'll notice there is a Write node for each element, connected to a Switch node. It is a very good idea to prerender your holdouts, import and connect them to the Switch node´s 1 input and switch to 1. We are done here.
By the way, one last thing, I'd use this convention for the Holdout pre-comp output:  <elementname_holdout>. For instance: "road_holdout".
AUTO COMP

Having obediently complied with above's procedure, and provided you chose to drop the dDots using the hotkey or the dDot menu as suggested, now you can auto comp all your CG elements together in literally less than a second just by clicking a button.

Instructions
1 - At the end of each Holdout setup there's a dot node automatically named according to the <elementname_holdout_version> rule. For instance: "road_holdout_0", "cones_holdout_0" etc...

2 - You will notice that, after creating the Holdout setup, all these dots are automatically selected. Now with the "b_deeptoolset" still open, just click on "Autocomp".
3 - Voila, a working auto comp of all your elements held out witch each other and disjoint-over merged together, in less than one second!!
4 - Now, If you reveal hidden connections (alt+a), you'll see that each Dot in your auto comp is connected to each CG element and its corresponding holdout, which is used as a mask for a "Multiply" node that multiplies by 0.
DEEP_UBERPASS

Sometimes, you will get a bunch of different CG elements with deep data that you would rather want to have combined in one big ass pass.

What the Uberpass tool does is precisely combining all your CG elements and its AOVS, on the one hand, and all the deep data, on the other.

This way you will still have all the sweetness of working with deep but with a reduced amount of complexity and overhead.

Instructions

1 - Select all your DeepRecolor (and only DeepRecolor ) nodes, launch the tool, and click on the "uberpass" button.

2 - As you see, there are two DeepMerge setups: one for rendering out all the combined color together with all the AOVS, and the other to DeepWrite out the combined deep data.

3 - Render those out and bring them back in the comp. Now you should have a combined color input with all AOVS and a new DeepRead node reading all combined deep data. Just connect them together with a DeepRecolor node and you are good to go.

DEPTH_FROM_DEEP
This tool is going to extract a clean depth pass from the deep data for later use in the comp along with your favorite z-defocus tool, for creating fog effects, for grading, you name it.
Instructions
1 - Select all your deepRecolor nodes and click on the "deep2depth" button. In less than a second, you should see a setup by the end of which you'll have an adequately processed depth channel ready to be pre-comped and used. IMPORTANT NOTE:  If you wanted to create both the Holdouts and the Deep2Depth setups, you need to create FIRST the Deep2Depth and then the Holdouts, otherwise the tool will make a mess and will fail. I will try to fix this bug in the next iteration!!
2 - By the way, notice how the tool has created a new set of DeepRecolor nodes, colored in pink. This is because, for the depth to be correct, we need a new set of DeepRecolor nodes with the "target input alpha"  NOT checked (as opposed to how it needs to be for the Holdouts setup). Otherwise, you'd have weird edge problems.
3 - Also, take a look at the Expression node's alpha channel expression: "Zdepth.red == 0 ? 15000 : Zdepth.red". Feel free to set up your 0 alpha value as convenient for your shot. By default is set to 15.000 units.

4 - Finally, just pre-comp it and use it where needed in the comp

Read more →

A FEW SIMPLE PYTHON TOOLS TO MAKE COMPING LIFE JUST (A BIT) EASIER.

tl;dr: some useful Python snippets for Nuke.

Here you guys have a bunch of little Python scripts to make some common comping tasks just a tiny bit easier. Nothing too ambitious though, just a few tricks that will buy you some time and make you look more pro, especially when used along with the amazing W_hotbox tool.
b_procedural_backdrop
This one is an extended version of a script I previously posted and will enable to procedurally create Backdrop Layouts that are nicely and evenly arranged along Nuke´s Nodegraph. Here is how:

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 shortcut.

Distribute the code

Download the script from the link below save it as a Python file called "b_procedural_backdrop.pywhere Nuke can find it. In other words, place it within the Nuke Plugin Path 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:

import b_procedural_backdrop.py
n_menu = nuke.menu('Nuke')
b_menu = n_menu.addMenu('MenuName')
b_menu.addCommand('b_procedural_backdrop.py', b_procedural_backdrop.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_procedural_backdrop' 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:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

As you can see, there are a few parameters to deal with:

  • Note Size: set your note size.
  • Label: set your label.
  • Align: left, right or center.
  • Color: pick your color, or throw the dice with random option.
  • Amount: how many backdrops you want.
  • Axis: horizontal or vertical layout.
  • Spacing: set the spacing between your backdrops

Here is a Backdrop Layout example to give you an idea of this would look like:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Additionally to the backdrops, as you can notice a dotted spine is created close to the layout so that you can use it as the main spine of your comp, in case that would be of your interest.

code
download b_procedural_backdrop
b_ibkstacker
This one will enable you to stack IBK Colour recursively in the blink of an eye.

1 - How to use it

After having installed the code and assigned it to a shortcut like in the first example, just select an IBK Colour Node and run the tool.  A Nuke Panel like this one should appear:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

As you can see, there are a few parameters to deal with:

  • ibk colour stack recursion limit: a drop-down menu that allows you to specify the recursion limit of your IBKColour Node stack.
  • add corrective roto: a checkbox that will merge a clamped roto under your first IBKColour to help clean up the clean plate.

Now simply select from the drop-down menu the recursion limit  and check the "add corrective roto" button if necessary. Having done that, click OK and in short time you will have a stack of IBK Colour Nodes linked via expressions to the first one, along with an IBK Gizmo connected both to the Clean Plate and to the Plate.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Alternatively, if you checked the "add corrective roto" checkbox, you would have something like this :

 

There you go, a fully automated IBK stacking system that will save you a brief time each time you use it.

 

 

code
download b_ibkstacker
b_distance_normalizer
Have you ever wanted to calculate the distance between two animated 3d points? I am pretty sure you have. Have you ever wanted to then normalize that distance so that it runs from 0 to 1 and thus be able to use it as a multiplier of some parameter in your comp? Maybe? OK, that is what this tool does exactly, it will calculate the distance between two 3d points (say a Camera Node and an Axis) and then it normalizes it so that it runs from 0 to 1.

1 - How to use it

This time we are dealing with a Gizmo that has two inputs: "inputcam" & "inputaxis". Note that you can plug any 3d Node that has a "Translate" Knob.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

On the properties bin you will find four knobs:

  • distance: displays the distance between the two inputs in world units.
  • normalized_distance: displays the normalized version of the distance.
  • normalize_distance: a Python Script Button that will run the code that calculates the normalized distance and creates an animated curve running from 0 to 1.
  • reset_curve: resets the normalized_distance curve to 0.

 

 

 

 

 

 

 

So now you are unstoppable when it comes to animating stuff depending on how close or far away a specific object may be. Not that bad, huh?

code
download b_distance_normalizer Gizmo download b_distance_normalizer Python Module
b_cam_puller
Often timeswhen dealing with a pgBokeh Node connected to a Camera Node, we need to set the "focal_distance" and "F-stop" knobs by hand to define the aperture and the focal point of the camera in order to have a correct DoF setup. This little tool will enable you to do those things in a slightly more streamlined way, especially if you would be provided with a list of focal distance values. Let's take a look:

1 - How to use it

After having installed the code and assigned it to a shortcut like in the first example, just run the tool.  A Nuke Panel like this one should appear:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

As you can see, there are a few parameters to deal with:

  • fstop: sets the fstop of the selected Camera Node.
  • frame_list: set here the frames you want to set focal distance values on.
  • focus plane values: focal distance values correlative to the corresponding frame values.
  • choose camera: lists all Camera Nodes in your Nuke Script.

Now click OK and the selected Camera Node will have the corresponding values set up. Open the curve editor and check out the selected Camera's  "focal_distance" knob.  It will have an animation curve with each of the focal plane values on each of the frames in the passed frame list.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

code
download b_cam_puller
b_keymix_this
This one will enable you to create a KeyMix setup in the blink of an eye.

1 - How to use it

After having installed the code and assigned it to a shortcut like in the first example, just select any node and run the tool:

before/after

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

There you go, a fully automated KeyMix setup system that will save you a few seconds time each time you use it. Not too much I know, but something is something, right?

code
Download b_key_mix_this

 

Read more →

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 TO GRIZMO

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 →

CAMERA FROM EXR

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 FROM 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 →