Nuke’s built in nuke.allNodes() function offers two very useful features- the ability to filter for a single node class, and the ability to recurse deep into groups. What it doesn’t offer is the ability to do both at once. If you set a filter class and recurseGroups=True, the recurseGroups argument will be ignored and only the first level results will be found. Also of importance is that Nuke attempts to derive what Group you want to search within if you don’t provide a group argument, but it usually guesses poorly. Let’s fix those bugs as easily as we can.
The Basics
Since we need to be a drop in replacement for nuke.allNodes(), we need to start with the same arguments, titled the same way, in the same order. This way you can import our allNodes() and have it override the nuke.allNodes().
Now let’s try for an exact passthrough of our given arguments. Nuke will trip us up here, as the default values for filter and group are not None, so we can’t just pass our exact received arguments in. Instead, we’ll construct a keyword argument dictionary, and pass that in.
The bug occurs when both recurseGroups=True and a filter argument are passed to nuke.allNodes(). Anything else can be given and returned straight from the native allNodes. It then makes sense to start our function off with an if clause, placing the parts of the function we’ve already created behind the else:
Then we need to begin constructing the meat of the fix. We have two choices:
Search for all group nodes with nuke.allNodes(), then execute nuke.allNodes() within those group nodes with the given filter argument.
Search for all nodes period, then construct & return a list containing only nodes which match our filter.
Let’s use timeit to figure out which runs faster.
Timing the Fixes
I have a script with a bunch of group nodes, and some other nodes hidden inside. A few are ColorWheels, and we’ll search for those.
importtimeit# Search for Groups, then search for wanted nodedefbygroup(filter):all_groups=nuke.allNodes('Group',recurseGroups=True)all_nodes=[]forsub_groupinall_groups:all_nodes.extend(nuke.allNodes(filter,sub_group))returnall_nodes# Search for all nodes, then use list comprehension to filterdeflistcomp(filter):all_nodes=nuke.allNodes(recurseGroups=True)return[nodefornodeinall_nodesifnode.Class()==filter]# Run each a million times# By Group ====================================================================timeit.timeit('bygroup("ColorWheel")',"from __main__ import bygroup",number=1000000)# Result:0.78045105934143066timeit.timeit('bygroup("ColorWheel")',"from __main__ import bygroup",number=1000000)# Result:0.79747390747070312timeit.timeit('bygroup("ColorWheel")',"from __main__ import bygroup",number=1000000)# Result:0.79068994522094727# =============================================================================# List Comprehension ==========================================================timeit.timeit('listcomp("ColorWheel")','from __main__ import listcomp',number=1000000)# Result:0.75563311576843262timeit.timeit('listcomp("ColorWheel")','from __main__ import listcomp',number=1000000)# Result:0.74588203430175781timeit.timeit('listcomp("ColorWheel")','from __main__ import listcomp',number=1000000)# Result:0.74688887596130371# =============================================================================
The results are close enough to not really matter, but we’ll still use the list comprehension method because it’s cleaner and faster.
Final allNodes()
Our final replacement for nuke.allNodes() looks like this:
defallNodes(filter=None,group=nuke.root(),recurseGroups=False):""" Wraps nuke.allNodes to allow filtering and recursing Args: filter=None : (str) A Nuke node name to filter for. Must be exact match. group=nuke.root() : (<nuke.nodes.Group>) A Nuke node of type `Group` to search within. If not provided, will begin the search at the root level. recurseGroups=False : (bool) If we should continue our search into any encountered group nodes. Returns: [<nuke.Node>] A list of any Nuke nodes found whose Class matches the given filter. Raises: N/A """# First we'll check if we need to execute our custom allNodes function.# If we don't have a filter AND recurseGroups=True, `nuke.allNodes` will# do the job fine.iffilterandrecurseGroups:# Search for every node, then filter using a list comprehension.# Faster than searching for all groups, then searching again# for the filter.all_nodes=nuke.allNodes(group=group,recurseGroups=True)return[nodefornodeinall_nodesifnode.Class()==filter]else:# We just need to execute Nuke's `nuke.allNodes` function.# But we need to modify our list of keyword arguments and remove# the filter argument if it wasn't passed, otherwise Nuke chokes.kwargs={'filter':filter,'group':group,'recurseGroups':recurseGroups}# Remove empty argumentsifnotfilter:delkwargs['filter']returnnuke.allNodes(**kwargs)