Last week I wrote about how to make your first little plugin for Nuke using Python.
It was a good start but it was missing a few functions to be fully done.

The biggest issue was that once a preset is created, you had no way to edit or delete it, without going to change it manually in the system files. That will be the first thing we will address today.
Secondly, I’d like to automatically name the colors the user picks, and present that as a name suggestion when creating a preset, instead of just “Color”. That might end up being a decent amount of work for not much, but it’s something I’ve been interested in for a while┬ánow.


Disclaimer: This is a pretty imposing amount of text and code for a beginner, and I understand if it scares you a bit. It stretched on more than I had originally hoped because I tried to explain as much as I could, and give examples that aren’t part of the final code. I hope you will forgive me and manage to get though.

Managing the Color Presets

The most instinctive and “nukest” way I can think about to manage the presets would be to open a Nuke Panel.

Before I even think about coding, I find it helpful to think about what my panel will contain and how it will look like. A good old paper notebook and pen help me lay that down.

A beautifully photographed Sketch of my future Panel

A beautifully photographed Sketch of my future Panel

The way I’m seeing it right now, is that each of the presets name will be displayed as an editable Text field, next to it will be a little color chip, and a checkbox for deleting.
At he bottom I may or may not add a “New Preset” button, I’m not too sure how I would handle that code wise. Finally, I’ll build that as a modalDialog, which will give me a Cancel and OK buttons for free.
If the user clicks OK, his changes will be applied, otherwise nothing will happen.

For coding, I will rely on this page from the documentation: http://docs.thefoundry.co.uk/nuke/80/pythondevguide/custom_panels.html
(I’m working in Nuke 8 at the moment)

The first issue I run into when thinking about my code, is that I’m not sure how many knobs I will have in my panel. There could be None, or there could be thousands..
The knobs building needs to be based on the same dictionary we used in part 1. Each 3 knobs (name, color, delete) also need to be linked to each other, so that when we run the code through at the end, we know which color it is that we would like to delete/edit.

It’s actually the first time I run into this issue, and I’m not too sure how to handle that.
When this happens, the best way to get unstuck is to browse the web for a solution, look for another tool using a similar function, or ask someone who knows.

(You can’t actually know, but at this point I paused the writing of the article for more than 24h. During these 24h, I did a lot of things completely unrelated, but I also spent a considerable amount of time thinking about the best way to approach this. I also sent an email to the nuke-python mail list to check the best way to approach this. No answers so far.)

I couldn’t find an example when someone had to do the same thing, so I just got into nuke and started coding a test Panel, based on the examples in the documentation.
I knew I would have to loop through my dictionary to create all the appropriate knobs. Since all the knobs for a specific dictionary entry would need to relate to each other, I decided to group them into a dictionary, then add this dictionary to a list.
Here’s my test code:

dict = {"Test01" : 50, "Test02" : 120}

class Test( nukescripts.PythonPanel ):
    def __init__( self, dict):
        nukescripts.PythonPanel.__init__( self, 'Test' )
        # CREATE KNOBS DICTS IN A LISTS
        self.list = []
        for entry in dict:
            knobDict = {"name" : nuke.String_Knob(entry, '', entry),
                        "value": nuke.Int_Knob( 'value_%s'% entry, 'Value' )}
            knobDict["value"].setValue(dict[entry])
            knobDict["value"].clearFlag(nuke.STARTLINE)
            self.list.append(knobDict)
            
        # ADD KNOBS
        for i in self.list:
            for k in i:
                self.addKnob( i[k] )

p = Test(dict)
if p.showModalDialog():
    print p.list

I’m pretty happy with the way this behaves. Note the “self.” in front of my list variable. This makes the variable accessible at the end with “p.list”. Without it I wasn’t able to access the list outside of my panel.

Note: I’m making a big mistake in this test code by naming my variable “dict”. I’m leaving it though as I’m trying to show you the whole process. See further down when I actually notice my mistake (just before I get to the color naming).

Now, let’s re-arrange the code to match better what we’re actually trying to do.

dict = {"Green": 16318464, "Pink" : 4288256256}

class ColorPresetPanel( nukescripts.PythonPanel ):
    def __init__( self, colors):
        nukescripts.PythonPanel.__init__( self, 'Manage Color Presets' )
        # CREATE KNOBS AS LISTS
        self.list = []
        for color in colors:
            knobDict = {"name" : nuke.String_Knob("name_%s" % color, '', color),
                              "value": nuke.ColorChip_Knob( 'value_%s'% color, '' ),
                              "delete": nuke.Boolean_Knob("delete_%s" % color, 'delete')}
            knobDict["value"].setValue(colors[color])
            knobDict["value"].clearFlag(nuke.STARTLINE)
            knobDict["delete"].clearFlag(nuke.STARTLINE)
            self.list.append(knobDict)
            
        # ADD KNOBS
        for i in self.list:
            for k in i:
                self.addKnob( i[k] )

p = ColorPresetPanel(dict)
if p.showModalDialog():
    print p.list
It's starting to look like the original sketch. I should set an icon for the top left..

It’s starting to look like the original sketch. There is an ugly icon in the corner, but so far I haven’t found the way to get rid of it. Only happens on windows..

In the original sketch, I had a button to add a new preset directly from this panel. I don’t think I actually want this in my final plugin, but since you’re now making the plugin yourself, if you do want it, you can code it yourself! There are a few things that will be tricky though, so if you’re lazy, you’re lucky, because I did write the code for you.

class ColorPresetPanel( nukescripts.PythonPanel ):
    def __init__( self, colors):
        nukescripts.PythonPanel.__init__( self, 'Manage Color Presets' )
        # CREATE KNOBS AS LISTS
        self.list = []
        for color in colors:
            knobDict = {"name" : nuke.String_Knob("name_%s" % color, '', color),
                              "value": nuke.ColorChip_Knob( 'value_%s'% color, '' ),
                              "delete": nuke.Boolean_Knob("delete_%s" % color, 'delete')}
            knobDict["value"].setValue(colors[color])
            knobDict["value"].clearFlag(nuke.STARTLINE)
            knobDict["delete"].clearFlag(nuke.STARTLINE)
            self.list.append(knobDict)
        self.addButton = nuke.Script_Knob( "+" )
        self.addButton.setFlag(nuke.STARTLINE)
        self.okButton = nuke.Script_Knob( "OK" )
        self.cancelButton = nuke.Script_Knob( "Cancel" )
        self.n = 1
            
        # ADD KNOBS 
        for i in self.list:
            for k in i:
                self.addKnob( i[k] )
        self.addKnob(self.addButton)
        self.addKnob(self.okButton)
        self.addKnob(self.cancelButton)

    def oneMore(self):
        knobDict = {"name" : nuke.String_Knob("name_new%i" % self.n, '', 'New Name'),
                           "value": nuke.ColorChip_Knob( 'value_new%i'% self.n, '' ),
                           "delete": nuke.Boolean_Knob("delete_new%i" % self.n, 'delete')}
        self.list.append(knobDict)
        # REMOVE 3 BUTTONS: Without that, the new field would be added after them.
        self.removeKnob(self.addButton)
        self.removeKnob(self.okButton)
        self.removeKnob(self.cancelButton)
        
        # ADD KNOBS 
        for k in knobDict:
            self.addKnob( knobDict[k] )
        self.n += 1
        # Put back the 3 buttons we removed
        self.addKnob(self.addButton)
        self.addKnob(self.okButton)
        self.addKnob(self.cancelButton)

    def knobChanged(self, knob):
        if knob in(self.addButton,):
            self.oneMore()

Processing the info from the panel

We now have a nicely working panel, but it isn’t actually doing anything yet.
Right now, we’re feeding a dictionary into the panel, it gets parsed into knobs. From the modified knobs, we should be able to parse a new updated dictionary.
For the test, I’m going to feed the panel a dictionary containing a wrong value (A magenta that has value of Cyan), and try to see if I can edit it.

dict = {"Blue": 65280, "Magenta": 16776960, "Green": 16711680, "Red": 4278190080}

class ColorPresetPanel( nukescripts.PythonPanel ):
    def __init__( self, colors):
        nukescripts.PythonPanel.__init__( self, 'Manage Color Presets' )
        # CREATE KNOBS AS LISTS
        self.list = []
        for color in sorted(colors):
            knobDict = {"name" : nuke.String_Knob("name_%s" % color, '', color),
                              "value": nuke.ColorChip_Knob( 'value_%s'% color, '' ),
                              "delete": nuke.Boolean_Knob("delete_%s" % color, 'delete')}
            knobDict["value"].setValue(colors[color])
            knobDict["value"].clearFlag(nuke.STARTLINE)
            knobDict["delete"].clearFlag(nuke.STARTLINE)
            self.list.append(knobDict)
            
        # ADD KNOBS
        for i in self.list:
            for k in i:
                self.addKnob( i[k] )

p = ColorPresetPanel(dict)
if p.showModalDialog():
    newColors = {}
    for preset in p.list:
        if not preset['delete'].value():
            newColors[preset['name'].value()] = preset['value'].value()
    print newColors
#I'm renaming Magenta to Cyan, and deleting Green
# Result:
{'Blue': 65280, 'Cyan': 16776960, 'Red': 4278190080L}

Cool, that worked. Notice how my ‘Red’ value got a L suffix. That’s just a way python 2 indicates this is a long int. It shouldn’t affect us at all.

Now let’s merge it all with our code from last week to get a final code (for this step).
Don’t forget to add a menu entry to open the panel.

import nuke, nukescripts
import json, os
 
# Settings
path = os.path.expanduser("~/.nuke/nodeColorPresets.json")
targetMenu = nuke.toolbar("Nodes")


# Custom Panel
class ColorPresetPanel( nukescripts.PythonPanel ):
    def __init__( self, colors):
        nukescripts.PythonPanel.__init__( self, 'Manage Color Presets' )
        # CREATE KNOBS AS LISTS
        self.list = []
        for color in sorted(colors):
            knobDict = {"name" : nuke.String_Knob("name_%s" % color, '', color),
                              "value": nuke.ColorChip_Knob( 'value_%s'% color, '' ),
                              "delete": nuke.Boolean_Knob("delete_%s" % color, 'delete')}
            knobDict["value"].setValue(colors[color])
            knobDict["value"].clearFlag(nuke.STARTLINE)
            knobDict["delete"].clearFlag(nuke.STARTLINE)
            self.list.append(knobDict)
            
        # ADD KNOBS
        for i in self.list:
            for k in i:
                self.addKnob( i[k] )


# Function to read the user presets.
def readColorPresets(path):
    if not os.path.isfile(path):
        print "No Color preset found "
        return {}
    else:
        try:
            f = open(path)
            colors = json.load(f)
            f.close()
            if type(colors) is dict:
                return colors
            else:
                print "The preset file doesn't contain a valid dictionary"
                return {}
        except:
            print "Error reading color preset file"
            return {}
 
# Function to write the user presets.
def writeColorPresets(path, colors):
    try:
        f = open(path, "w")
        json.dump(colors, f)
        f.close()
        return True
    except:
        return False
 
#Function to change tile_color:
def setTileColor(value = None):
    '''
    Changes the tile_color of multiple nodes at once
    '''
    if value == None:
        value = nuke.getColor()
    for node in nuke.selectedNodes():
        node.knob('tile_color').setValue(value)
 
#Function to create the Menu:
def createTileColorMenu():
    # Loading out color presets
    colors = readColorPresets(path)
 
    # And creating the menu.
    colorMenu = targetMenu.addMenu( 'Color Nodes', icon="color_node.png")
 
    # Populating the Menu
    for color in sorted(colors):
        colorMenu.addCommand(color, lambda: setTileColor(colors[color]))

    colorMenu.addCommand("Custom Color", lambda: setTileColor())
    colorMenu.addCommand("-", "", "")
    colorMenu.addCommand("Add New Preset", lambda: addNewColor())
    colorMenu.addCommand("Manage Presets", lambda: manageColorPresets())
 
#Function to add a color preset:
def addNewColor():
    #Open a color picker panel.
    color = nuke.getColor()
    if color:
        #Load our colors from the file
        colors = readColorPresets(path)
        validName = False
        while not validName:
            name = nuke.getInput("Give a name to your color","color")
            if name:
                #Verify the name isn't taken. Reopen the panel if it's already taken
                if not name in colors.keys():
                    validName = True # Get out of the loop
                    #add entry to the dictionary
                    colors[name] = color
                    success = writeColorPresets(path, colors)
                    if success:
                        targetMenu.findItem("Color Nodes").clearMenu()
                        createTileColorMenu()
                    else:
                        print "Error writing file %s" % path
                else:
                    nuke.message("The name already exists")

# Function to manage the presets:
def manageColorPresets():
    colors = readColorPresets(path)
    p = ColorPresetPanel(colors)
    if p.showModalDialog():
        newColors = {}
        for preset in p.list:
            if not preset['delete'].value():
                newColors[preset['name'].value()] = preset['value'].value()
        success = writeColorPresets(path, newColors)
        if success:
            targetMenu.findItem("Color Nodes").clearMenu()
            createTileColorMenu()
        else:
            print "Error writing file %s" % path

createTileColorMenu()

I seem to get an occasional bug, sometimes the error “# Result: The preset file doesn’t contain a valid dictionary” shows up.
This is what I’m setting to display when the variable I load from the JSON file is not a dictionary. Somehow sometimes it gets triggered even though the variable is indeed a dict.
A new session of Nuke fixes the bug.

It’s because in my first tests of code I named one of my variables “dict” which was not the smartest idea.
By doing that I’m overwriting a default Python object.
Now instead of checking if my variable is a dict, it’s checking if it is that variable I set (and of course it isn’t). If I had been coding in a real Python IDE, “dict” would have been highlighted, and I would have noticed that’s something I shouldn’t use as a variable name. Nuke’s script editor’s highlighting is a bit limited, and I didn’t realize my mistake until running the complete code.

I did some very minor changes to the code, like adding a divider line to the Menu, and sorting the dictionary in the panel so the order of the colors is the same as the order in the menu.

Automatically Naming the Colors

We could argue that naming a color isn’t so painful to do Manually, but I’ve been interested for a while in algorithms to match colors. This would be a good exercise to practice using these algorithms.
It might be going a bit past a beginner level, but I shouldn’t be too hard to understand.

How to name the colors

My first intuition to name the colors would have been to get inspired by the way After Effects automatically names colors.
I don’t actually have a copy of After Effects so I’m not able to check exactly how it was behaving, but out of memory, it would name the basic color (Red, Green, Blue, Yellow, Purple, …) and add a Descriptive word if necessary (Dark, Pale, Bright, …).

HSV Cylinder – Source Wikipedia

Converting our color value to HSV, it would actually be relatively easy generate such a name. It’s quite boring to type all the if/else statements, but at the end you get a relatively accurate method, that is also extremely easy to understand for a beginner. It can also become more and more accurate by dividing your sections more. (You will see I got lazy on the blue colors and only categorized them as one tint)

def nameColor(hue, saturation, value):
    colorName = ''
    if value == 0:
        colorName = 'Black'
        return colorName
    if value == 1 and saturation == 0:
        colorName = 'White'
        return colorName

    if value <= 0.3:
        colorName+= 'Dark '
    elif value >= 0.9:
        colorName+= 'Bright '
    
    if saturation == 0:
        colorName += 'Grey'
        return colorName
    elif saturation <= 0.15:
        colorName+= 'Greyish '
    elif saturation <= 0.6:
        colorName+= 'Pale '
    elif saturation >= 0.9:
        colorName+= 'Vivid '

    if hue <= 0.02 or hue >= 0.95:
        colorName += 'Red'
    elif hue <= 0.11:
        colorName += 'Orange'
    elif hue <= 0.17:
        colorName += 'Yellow'
    elif hue <= 0.29:
        colorName += 'Apple Green'
    elif hue <= 0.42:
        colorName += 'Green'
    elif hue <= 0.46:
        colorName += 'Cyan Green'
    elif hue <= 0.52:
        colorName += 'Cyan'
    elif hue <= 0.7:
        colorName += 'Blue'
    elif hue <= 0.79:
        colorName += 'Purple'
    elif hue <= 0.9:
        colorName += 'Pink'
    else:
        colorName += 'Magenta'
    return colorName

print nameColor(0.78, 0.42, 0.39)
print nameColor(0.35, 1, 0.95)

This is a totally valid approach, but in my case, I’d like to try something a little bit more complicated.
I would like my color to get matched to the closest known color taken from a list of colors.
Think about what happens when you export a GIF that has a limited amount of allowed colors. Each pixel gets recolored to the closest matching allowed color.
Python would be a bit slow to run such a script on each pixel of an image, but on a single color value why not?

It happens that Wikipedia has a pretty big list of colors with many different names here: List of colors (compact)
I also found a page by guy named Gauthier Lemoine who extracted the list from Wikipedia and made it into a JSON file for javascript, but the good news is, it’s compatible with Python as well.
He made an online tool to name a color that you can find by clicking here. His approach described in the page will be the base of what’s following.
With further reading (notably this link) I decided I’d do my matching in YUV colorspace instead of RGB or HSL. It is supposedly better matching human perception (yet far from perfect).

Doing the matching

Python has a colorsys module that can handle color conversions between different color systems like RGB, HSV and YIQ. It doesn’t have YUV, but according to wikipedia YIQ sounds pretty close to YUV, and a bit of googling around makes me believe it should work just as well, if not better than YUV for some tones, so I’ll go with that instead.

From now on, for the rest of this tutorial, I’ll be thinking of colors as a point in a 3D coordinate system. Better yet, we an consider each color to be a 3D vector.

0.1% of all RGB values represented as a point cloud.

0.1% of all RGB values represented as a point cloud.

The same color values, but in a YIQ space. Supposedly it will yield better results for the color matching.

The same color values, but in a YIQ space. Supposedly it will yield better results for the color matching.

To generate these point clouds, I also used python, with a code inspired by this: http://hagbarth.net/?p=663 (Check out this guy’s blog, he’s got some impressive tools in there).
It’s not needed for coding the tool, but I thought it would help you visualize.

Now remember we had a list of colors from Wikipedia/Gauthier earlier, containing RGB values. I’m going to run that through python and make it into a YIQ list. The RGB values also were based on a 0-255 range, but I need a float value, so I have to keep that in mind.

import colorsys, json
# Reading the list
file = open("C:\Users\Erwan\Desktop\colors.json")
data = json.load(file)
file.close()

yiqData = []
for color in data:
    rgb = (color['x']/255.0, color['y']/255.0, color['z']/255.0) #add .0 to make sure the math happens in float
    yiq = colorsys.rgb_to_yiq(rgb[0],rgb[1],rgb[2])
    newColor = {'x': yiq[0], 'y':yiq[1], 'z':yiq[2], 'label': color['label']}
    yiqData.append(newColor)

# Writing the new list
file = open("C:\Users\Erwan\Desktop\colorsYIQ.json", "w")
json.dump(yiqData, file)
file.close()

One drawback is that the JSON file is much heavier in YIQ format than RGB 8bit because the format takes more characters to write (almost double).
ex:
{“x”:93,”y”:138,”z”:168,”label”:”Air Force blue”}
{“y”: -0.1435294117647059, “x”: 0.5011764705882353, “z”: -0.0005882352941176949, “label”: “Air Force blue”}
In our case the files are still quite small, so it won’t make much difference, but if we had a much bigger dataset, it would be something to consider.
Get the final YIQ JSON file

The samples for which we have a name, in RGB space.

The samples for which we have a name, in RGB space.

The same samples, in YIQ space.

The same samples, in YIQ space.

Now let’s try to match a single color to one of the labels.
I randomly picked a color 0.75, 0.4, 0.15 (rgb float) that I would probably have named “Pale Tangerine” if I was the one making the names.

My single color in a YIQ lonely space.

My single color in a YIQ lonely space.

Even though all this may sound a bit complex at first, now finding the closest value is pretty straight forward.
We need to measure the distance between this point and each of the known points, and return the closest one.

To measure the distance, it’s basic vector math. We need to subtract our two vectors, then calculate the magnitude of the resulting vector. https://www.mathsisfun.com/algebra/vectors.html
We can either do it manually:

from math import sqrt, pow
x1, y1, z1 = 15,12,45 #my first vector
x2, y2, z2 = 75,12,4 #my second vector

new_x = x1-x2
new_y = y1-y2
new_z = z1-z2

distance = sqrt(pow(new_x,2)+pow(new_y,2)+pow(new_z,2))
print distance
# Result:
72.6704891961

Or we can use the Nuke Math module, which is not very well documented, but has a good tutorial on nukepedia here: http://www.nukepedia.com/written-tutorials/using-the-nukemath-python-module-to-do-vector-and-matrix-operations/

vectorA = nuke.math.Vector3(15,12,45)
vectorB = nuke.math.Vector3(75,12,4)

distance = vectorA.distanceBetween(vectorB)
print distance
# Result:
72.6704864502

As you can see, both of these methods have a slightly different result, most likely a rounding error. It’s very little and won’t affect us. I’m going to use the nuke vector3 object for it’s ease of use.

import colorsys, json

def nameColor(r,g,b):
    pathToNames = "C:\Users\Erwan\Desktop\colorsYIQ.json"

    yiq = colorsys.rgb_to_yiq(r,g,b)
    x,y,z =  yiq[0], yiq[1], yiq[2]
    vectorA = nuke.math.Vector3(x,y,z)
    name = ''
    minDist = None
    try:
        file = open(pathToNames)
        data = json.load(file)
        file.close()
    except:
        return "Unknown Color"
    for color in data:
        vectorB = nuke.math.Vector3(color['x'],color['y'],color['z'])
        dist = vectorA.distanceBetween(vectorB)
        if minDist == None or dist < minDist:
            minDist = dist
            name = color['label']
    return name
    
print nameColor(0.75,0.4,0.15)
# Result:
Ruddy brown

That code works, and despite the fact that it has to run once for each value, it’s still almost instantaneous to get the result.
Now, I’ll integrate it with the rest of the plugin:

Final code:

import nuke, nukescripts
import json, os
 
# Settings
path = os.path.expanduser("~/.nuke/nodeColorPresets.json")
pathToNames = "C:/Users/Erwan/Desktop/colorsYIQ.json"
targetMenu = nuke.toolbar("Nodes")


# Custom Panel
class ColorPresetPanel( nukescripts.PythonPanel ):
    def __init__( self, colors):
        nukescripts.PythonPanel.__init__( self, 'Manage Color Presets' )
        # CREATE KNOBS AS LISTS
        self.list = []
        for color in sorted(colors):
            knobDict = {"name" : nuke.String_Knob("name_%s" % color, '', color),
                              "value": nuke.ColorChip_Knob( 'value_%s'% color, '' ),
                              "delete": nuke.Boolean_Knob("delete_%s" % color, 'delete')}
            knobDict["value"].setValue(colors[color])
            knobDict["value"].clearFlag(nuke.STARTLINE)
            knobDict["delete"].clearFlag(nuke.STARTLINE)
            self.list.append(knobDict)
            
        # ADD KNOBS
        for i in self.list:
            for k in i:
                self.addKnob( i[k] )
                
# Function to name a color automatically:
def nameColor(r,g,b):
    yiq = colorsys.rgb_to_yiq(r,g,b)
    x,y,z =  yiq[0], yiq[1], yiq[2]
    vectorA = nuke.math.Vector3(x,y,z)
    name = ''
    minDist = None
    try:
        file = open(pathToNames)
        data = json.load(file)
        file.close()
    except:
        print "Error loading Color labels"
        return "Unknown Color"
        
    for color in data:
        vectorB = nuke.math.Vector3(color['x'],color['y'],color['z'])
        dist = vectorA.distanceBetween(vectorB)
        if minDist == None or dist < minDist:
            minDist = dist
            name = color['label']
    return name

# Function to convert nuke HEX to RGB
def nukeHEXtoRGB(nuke_hex):
    try:
        real_hex = '%08x' % nuke_hex
        r = int(real_hex[0:2], 16)/255.0
        g = int(real_hex[2:4], 16)/255.0
        b = int(real_hex[4:6], 16)/255.0
    except:
        return None, None, None
    return r,g,b
    

# Function to read the user presets.
def readColorPresets(path):
    if not os.path.isfile(path):
        print "No Color preset found "
        return {}
    else:
        try:
            f = open(path)
            colors = json.load(f)
            f.close()
            if type(colors) is dict:
                return colors
            else:
                print "The preset file doesn't contain a valid dictionary"
                return {}
        except:
            print "Error reading color preset file"
            return {}
 
# Function to write the user presets.
def writeColorPresets(path, colors):
    try:
        f = open(path, "w")
        json.dump(colors, f)
        f.close()
        return True
    except:
        return False
 
#Function to change tile_color:
def setTileColor(value = None):
    '''
    Changes the tile_color of multiple nodes at once
    '''
    if value == None:
        value = nuke.getColor()
    for node in nuke.selectedNodes():
        node.knob('tile_color').setValue(value)
 
#Function to create the Menu:
def createTileColorMenu():
    # Loading out color presets
    colors = readColorPresets(path)
 
    # And creating the menu.
    colorMenu = targetMenu.addMenu( 'Color Nodes', icon="color_node.png")
 
    # Populating the Menu
    for color in sorted(colors):
        colorMenu.addCommand(color, 'setTileColor(%s)'% colors[color])

    colorMenu.addCommand("Custom Color", lambda: setTileColor())
    colorMenu.addCommand("-", "", "")
    colorMenu.addCommand("Add New Preset", lambda: addNewColor())
    colorMenu.addCommand("Manage Presets", lambda: manageColorPresets())
 
#Function to add a color preset:
def addNewColor():
    #Open a color picker panel.
    color = nuke.getColor()
    if color:
        #Load our colors from the file
        colors = readColorPresets(path)
        validName = False
        r,g,b = nukeHEXtoRGB(color)
        while not validName:
            name = nuke.getInput("Give a name to your color",nameColor(r,g,b))
            if name:
                #Verify the name isn't taken. Reopen the panel if it's already taken
                if not name in colors.keys():
                    validName = True # Get out of the loop
                    #add entry to the dictionary
                    colors[name] = color
                    success = writeColorPresets(path, colors)
                    if success:
                        targetMenu.findItem("Color Nodes").clearMenu()
                        createTileColorMenu()
                    else:
                        print "Error writing file %s" % path
                else:
                    nuke.message("The name already exists")

# Function to manage the presets:
def manageColorPresets():
    colors = readColorPresets(path)
    p = ColorPresetPanel(colors)
    if p.showModalDialog():
        newColors = {}
        for preset in p.list:
            if not preset['delete'].value():
                newColors[preset['name'].value()] = preset['value'].value()
        success = writeColorPresets(path, newColors)
        if success:
            targetMenu.findItem("Color Nodes").clearMenu()
            createTileColorMenu()
        else:
            print "Error writing file %s" % path

createTileColorMenu()

Thanks for reading through.
I will soon post this little plugin on Nukepedia for all to enjoy without having to make it yourself.