Download a panel done in PySide & QtPy
In this quick explanation, I will show you how to create the same panel in PySide, PySide2 and QtPy to change colors of nodes and other elements for Nuke (special thanks to Alexey Kuchinski).
You can download the panel for Nuke here:
Download Color Panel
At this link you can find an example with QtPy.
Useful Links
- https://pypi.org/project/PySide2/
- www.nukepedia.com/prepare-for-qt5
- http://learn.foundry.com/nuke/developers/63/pythondevguide/custom_panels.html
- http://wiki.qt.io/Differences_Between_PySide_and_PyQt
- https://support.foundry.com/hc/en-us/articles/360000218324-Q100398-Implementing-PySide-scripts-into-Nuke-11
- https://davemne.wordpress.com/2017/08/07/nuke-11-and-pyside2/
- https://www.qt.io/
- https://fredrikaverpil.github.io/2016/07/25/developing-with-qt-py/
- http://learn.foundry.com/nuke/developers/63/pythondevguide/custom_panels.html
- https://davemne.wordpress.com/2017/08/07/nuke-11-and-pyside2/
QT
Why QtPy?
Qt is a cross-platform application framework and widget toolkit for creating classic and embedded graphical user interfaces, and applications that run on various software and hardware platforms with little or no change in the underlying codebase, while still being a native application with native capabilities and speed. Qt is currently being developed both by The Qt Company, a publicly listed company, and the Qt Project under open-source governance, involving individual developers and firms working to advance Qt. Qt is available with both proprietary[4] and open source GPL 2.0, GPL 3.0, and LGPL 3.0 licenses.
PyQt provides two different APIs, the first of which provides QStrings, QVariants, etc as is in Python. The new API 2 provides automatic conversion between the Qt classes and respective native Python datatypes and is much more Pythonic in nature. PyQt on Python 2.x defaults to API 1, while PyQt on Python 3 defaults to API 2.
PySide only supports PyQt's API 2 for details. Therefore Qt classes such as QStrings, QStringLists, and QVariants are not available on PySide. Instead, you should simply use native Python datatypes.
If you're porting code from PyQt, you might want to first modify the PyQt code to use API 2 (using a sip.setapi(class,ver) call before importing PyQt4), and only after getting that change working, change the imports to use PySide instead.
How to Install Qt.py
Click here to read how to install QtPy
from Qt import QtWidgets
from Qt import QtCompat
from Qt import QtGui
from Qt import QtCore
PySide
Why PySide and PySide2?
PySide2 is a Python binding of the cross-platform GUI toolkit Qt, currently developed by The Qt Company under the Qt for Python project. It is one of the alternatives to the standard library package Tkinter. Like Qt, PySide2 is free software. The project started out using Boost.Python from the Boost C++ Libraries for the bindings and later switched to the binding generator Shiboken to reduce the size of the binaries and the memory footprint.
PySide was released under the LGPL in August 2009 by Nokia, the former owners of the Qt toolkit, after Nokia failed to reach an agreement with PyQt developers Riverbank Computing[7] to change its licensing terms to include LGPL as an alternative license.
Work is currently underway to officially launch PySide2 as a Qt product, after all the effort on porting PySide to work with Qt 5.
PySide2 supports Linux/X11, Mac OS X, Windows and Maemo. Support for Android is currently being added by the PySide community.
PySide2 and Nuke11?
When launching a Python script with Nuke 11, if your Nuke script requires Python's PySide module then Nuke will throw an exception which will prevent it from opening.
With the release of Nuke 11, there have been significant library updates done with the aim of making Nuke VFX Reference Platform 2017 complaint.
In the case of the PySide, this was updated from PySide 1.2.2 to PySide 2.0 within the Nuke core libraries, so it can now be imported as Pyside2 rather than PySide. For more information on the library versions shipped with Nuke 11. As a result of this update from PySide to PySide2, starting with Nuke 11 PySide modules are no longer callable.
So, that's why in the new Python script it's always better to check which version of Nuke you are using.
#EXAMPLE 01
if nuke.NUKE_VERSION_MAJOR < 11:
#Nuke < 11
from PySide import QtGui, QtCore
else:
#Nuke >= 11
from PySide2 import QtGui, QtCore
#---------------------------------------------------------
#EXAMPLE 02
if nuke.NUKE_VERSION_MAJOR < 11:
from PySide.QtGui import *
from PySide.QtCore import *
from PySide.QtUiTools import QUiLoader
from PySide import QtCore, QtGui, QtUiTools, QtGui as QtWidgets
else:
from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtUiTools import QUiLoader
from PySide2 import QtCore, QtGui, QtUiTools, QtGui, QtWidgets
Example with Nodes
This is the final Color Panel created in this tutorial.
With this panel, selecting the nodes you can modify the colors. This is just an example:
Select all the nodes, click on the green button and see the result.
Now let's select a color in the first Grid of the Color Panel. We will get all the nodes of the same color of the Merge nodes.
You can also select a custom color
Example with Backdrops and StickyNotes
Selecting Backdrops and StickyNotes, you can also change the color of them
Do you want to change colors?
You can change colors of the ColorPanel if you don't like them. Just open the file ColorPanel.ui with QtDesigner. Click on the buttons and change the attribute background-color.
Tricks
1. Load file .ui from the same folder of the file .py:
thisFileDir = os.path.dirname(os.path.realpath(__file__))
file_interface = os.path.join(thisFileDir, "ColorPanel.ui")
2. If you wanna have the Panel always on top:
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
3. Change color of a Node (color has to be hex):
nuke.selectedNode().knob('tile_color').setValue(312364891)
4. Convert from RGB to Hex. RGB is a number in the range 0-1
weird = int('%02x%02x%02x%02x' % (red*255,green*255,blue*255,1),16)
5. Get color of the Node Class
nuke.defaultNodeColor(n.Class())
#if result is 0, then it's the default colors
6. Get color of the Node
nuke.selectedColor()['tile_color'].getValue()
#if result is 0, then it's the default colors
7. Set color of the Node
for n in nuke.selectedNodes():
n['tile_color'].setValue('insert INT color in hex')
8. Open a website in Python
import webbrowser
webbrowser.open('http://www.andreageremia.it/tutorial_color_panel.html')
9. Read selected Node from Group
#nuke.selectedNode().selectedNodes()
try:
nodeSelected = nuke.selectedNode()
if (nodeSelected.Class() == "Group" and nodeSelected.selectedNodes()):
#if node selected is a group, then check if anything is selected inside
nuke.message("Changing color of Nodes inside Group: " + nodeSelected.name())
nodeSelected = nodeSelected.selectedNodes()
else:
nodeSelected = nuke.selectedNodes()
except:
nuke.message("SELECT A NODE.\nIf you want to change color of Nodes inside a Group, please select also the Group in the Node Graph.")
Code in QtPy
Differences between the version QtPy and PySide have the highlight:
#****************************************************************
#************************ THIS IS DIFFERENT**************************
#****************************************************************
import os
import nuke
#****************************************************************
#*********************** THIS IS DIFFERENT***********************
#****************************************************************
from Qt import QtWidgets
from Qt import QtCompat
from Qt import QtGui
from Qt import QtCore
import webbrowser
#read the file UI from the same folder
thisFileDir = os.path.dirname(os.path.realpath(__file__))
file_interface = os.path.join(thisFileDir, "ColorPanel.ui")
global color_copied
color_copied = None
#---------------------------------------------------------
#****************************************************************
#*********************** THIS IS DIFFERENT***********************
#****************************************************************
class MyWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.main_widget = QtCompat.loadUi(file_interface)
self.setCentralWidget(self.main_widget)
self.setWindowTitle("ColorPanel")
#windows always on top
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
#set Fixed Sizes
self.setFixedWidth(370)
self.setFixedHeight(678)
self.load_ui()
def load_ui(self):
self.button1 = self.main_widget.findChild(QtWidgets.QPushButton, 'button1')
self.button1.clicked.connect(lambda: self.changeColorNode(self.button1.styleSheet()))
self.button2 = self.main_widget.findChild(QtWidgets.QPushButton, 'button2')
self.button2.clicked.connect(lambda: self.changeColorNode(self.button2.styleSheet()))
self.button3 = self.main_widget.findChild(QtWidgets.QPushButton, 'button3')
self.button3.clicked.connect(lambda: self.changeColorNode(self.button3.styleSheet()))
self.button4 = self.main_widget.findChild(QtWidgets.QPushButton, 'button4')
self.button4.clicked.connect(lambda: self.changeColorNode(self.button4.styleSheet()))
self.button5 = self.main_widget.findChild(QtWidgets.QPushButton, 'button5')
self.button5.clicked.connect(lambda: self.changeColorNode(self.button5.styleSheet()))
self.button6 = self.main_widget.findChild(QtWidgets.QPushButton, 'button6')
self.button6.clicked.connect(lambda: self.changeColorNode(self.button6.styleSheet()))
#.
#.
#.
self.button56 = self.main_widget.findChild(QtWidgets.QPushButton, 'button56')
self.button56.clicked.connect(lambda: self.changeColorNode(self.button56.styleSheet()))
self.button57 = self.main_widget.findChild(QtWidgets.QPushButton, 'button57')
self.button57.clicked.connect(lambda: self.changeColorNode(self.button57.styleSheet()))
self.button58 = self.main_widget.findChild(QtWidgets.QPushButton, 'button58')
self.button58.clicked.connect(lambda: self.changeColorNode(self.button58.styleSheet()))
self.button59 = self.main_widget.findChild(QtWidgets.QPushButton, 'button59')
self.button59.clicked.connect(lambda: self.changeColorNode(self.button59.styleSheet()))
self.button60 = self.main_widget.findChild(QtWidgets.QPushButton, 'button60')
self.button60.clicked.connect(lambda: self.changeColorNode(self.button60.styleSheet()))
self.customColor = self.main_widget.findChild(QtWidgets.QPushButton, 'customColor')
self.customColor.clicked.connect(self.changeColorNodeCustom)
self.restoreColor = self.main_widget.findChild(QtWidgets.QPushButton, 'restoreColor')
self.restoreColor.clicked.connect(self.restoreOriginalColor)
self.copyButton = self.main_widget.findChild(QtWidgets.QPushButton, 'copyButton')
self.copyButton.clicked.connect(self.copy_color)
self.pasteButton = self.main_widget.findChild(QtWidgets.QPushButton, 'pasteButton')
self.pasteButton.clicked.connect(self.paste_color)
self.infoButton = self.main_widget.findChild(QtWidgets.QPushButton, 'infoButton')
self.infoButton.clicked.connect(self.website)
self.checkboxClose = self.main_widget.findChild(QtWidgets.QCheckBox, 'checkboxClose')
#if clicked button
def changeColorNode(self, color):
color = color.replace('background-color: rgb','').replace(';','').replace('(','').replace(')','').replace(' ','')
r,g,b = color.split(',')
#convert from RGB to Hex
red = float(float(r)/255)
green = float(float(g)/255)
blue = float(float(b)/255)
hexColour = '%02x' % (red*255) + '%02x' % (green*255) + '%02x' % (blue*255)
weird = int('%02x%02x%02x%02x' % (red*255,green*255,blue*255,1),16)
#weird = int(hexColour,16)
print "COLOR: " + str(weird)
#print self.button.palette().color(QtGui.QPalette.Background).getRgb()
for node in nuke.selectedNodes():
node.knob('tile_color').setValue(weird)
self.closeWindow()
#---------------------------------------------------------
#if clicked custom color button
def changeColorNodeCustom(self):
col = nuke.getColor()
if col:
for n in nuke.selectedNodes():
n['tile_color'].setValue(col)
n['gl_color'].setValue(col)
print "COLOR: " + str(col)
self.closeWindow()
#---------------------------------------------------------
#COPY
def copy_color(self):
global color_copied
if len(nuke.selectedNodes()) >= 1:
n = nuke.selectedNode()
if n['tile_color'].getValue()==0:
color_copied = nuke.defaultNodeColor(n.Class())
else:
color_copied = int(n['tile_color'].getValue())
else:
nuke.message("select a node")
if color_copied is None:
color_copied = 0
print "COPIED COLOR: " + str(color_copied)
#PASTE
def paste_color(self):
if color_copied is not None:
for n in nuke.selectedNodes():
n['tile_color'].setValue(color_copied)
#---------------------------------------------------------
#www
def website(self):
webbrowser.open('http://www.andreageremia.it/tutorial_color_panel.html') # Go to the website
#---------------------------------------------------------
#close window
def closeWindow(self):
if self.checkboxClose.isChecked():
self.close()
#GOOOOOOOOOOOOOOOOOO!
def colorPanel():
my_window = MyWindow()
my_window.show()
#colorPanel()
Code in PySide and PySide2
import os
import nuke
import webbrowser
#****************************************************************
#*********************** THIS IS DIFFERENT***********************
#****************************************************************
if nuke.NUKE_VERSION_MAJOR < 11:
from PySide.QtGui import *
from PySide.QtCore import *
from PySide.QtUiTools import QUiLoader
from PySide import QtCore, QtGui, QtUiTools, QtGui as QtWidgets
else:
from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtUiTools import QUiLoader
from PySide2 import QtCore, QtGui, QtUiTools, QtGui, QtWidgets
thisFileDir = os.path.dirname(os.path.realpath(__file__))
file_interface = os.path.join(thisFileDir, "ColorPanel.ui")
global color_copied
color_copied = None
#****************************************************************
#*********************** THIS IS DIFFERENT***********************
#****************************************************************
class MyWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.main_widget = self.load_ui(file_interface)
self.setCentralWidget(self.main_widget)
self.setWindowTitle("ColorPanel")
#windows always on top
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
#set Fixed Sizes
self.setFixedWidth(361)
self.setFixedHeight(628)
self.load_ui_elements()
#****************************************************************
#*********************** THIS IS DIFFERENT***********************
#****************************************************************
def load_ui(self, ui_file):
loader = QUiLoader()
file = QFile(ui_file)
file.open(QFile.ReadOnly)
myWidget = loader.load(file, None)
file.close()
return myWidget
def load_ui_elements(self):
self.button1 = self.main_widget.findChild(QtWidgets.QPushButton, 'button1')
self.button1.clicked.connect(lambda: self.changeColorNode(self.button1.styleSheet()))
self.button2 = self.main_widget.findChild(QtWidgets.QPushButton, 'button2')
self.button2.clicked.connect(lambda: self.changeColorNode(self.button2.styleSheet()))
self.button3 = self.main_widget.findChild(QtWidgets.QPushButton, 'button3')
self.button3.clicked.connect(lambda: self.changeColorNode(self.button3.styleSheet()))
self.button4 = self.main_widget.findChild(QtWidgets.QPushButton, 'button4')
self.button4.clicked.connect(lambda: self.changeColorNode(self.button4.styleSheet()))
self.button5 = self.main_widget.findChild(QtWidgets.QPushButton, 'button5')
self.button5.clicked.connect(lambda: self.changeColorNode(self.button5.styleSheet()))
self.button6 = self.main_widget.findChild(QtWidgets.QPushButton, 'button6')
self.button6.clicked.connect(lambda: self.changeColorNode(self.button6.styleSheet()))
#.
#.
#.
self.button56 = self.main_widget.findChild(QtWidgets.QPushButton, 'button56')
self.button56.clicked.connect(lambda: self.changeColorNode(self.button56.styleSheet()))
self.button57 = self.main_widget.findChild(QtWidgets.QPushButton, 'button57')
self.button57.clicked.connect(lambda: self.changeColorNode(self.button57.styleSheet()))
self.button58 = self.main_widget.findChild(QtWidgets.QPushButton, 'button58')
self.button58.clicked.connect(lambda: self.changeColorNode(self.button58.styleSheet()))
self.button59 = self.main_widget.findChild(QtWidgets.QPushButton, 'button59')
self.button59.clicked.connect(lambda: self.changeColorNode(self.button59.styleSheet()))
self.button60 = self.main_widget.findChild(QtWidgets.QPushButton, 'button60')
self.button60.clicked.connect(lambda: self.changeColorNode(self.button60.styleSheet()))
self.customColor = self.main_widget.findChild(QtWidgets.QPushButton, 'customColor')
self.customColor.clicked.connect(self.changeColorNodeCustom)
self.restoreColor = self.main_widget.findChild(QtWidgets.QPushButton, 'restoreColor')
self.restoreColor.clicked.connect(self.restoreOriginalColor)
self.copyButton = self.main_widget.findChild(QtWidgets.QPushButton, 'copyButton')
self.copyButton.clicked.connect(self.copy_color)
self.pasteButton = self.main_widget.findChild(QtWidgets.QPushButton, 'pasteButton')
self.pasteButton.clicked.connect(self.paste_color)
self.infoButton = self.main_widget.findChild(QtWidgets.QPushButton, 'infoButton')
self.infoButton.clicked.connect(self.website)
self.checkboxClose = self.main_widget.findChild(QtWidgets.QCheckBox, 'checkboxClose')
#if clicked button
def changeColorNode(self, color):
color = color.replace('background-color: rgb','').replace(';','').replace('(','').replace(')','').replace(' ','')
r,g,b = color.split(',')
#convert from RGB to Hex
red = float(float(r)/255)
green = float(float(g)/255)
blue = float(float(b)/255)
hexColour = '%02x' % (red*255) + '%02x' % (green*255) + '%02x' % (blue*255)
weird = int('%02x%02x%02x%02x' % (red*255,green*255,blue*255,1),16)
#weird = int(hexColour,16)
print weird
#print self.button.palette().color(QtGui.QPalette.Background).getRgb()
for node in nuke.selectedNodes():
node.knob('tile_color').setValue(weird)
self.closeWindow()
#---------------------------------------------------------
#if clicked custom color button
def changeColorNodeCustom(self):
col = nuke.getColor()
if col:
for n in nuke.selectedNodes():
n['tile_color'].setValue(col)
n['gl_color'].setValue(col)
print "COLOR: " + str(col)
self.closeWindow()
#---------------------------------------------------------
#COPY
def copy_color(self):
global color_copied
if len(nuke.selectedNodes()) >= 1:
n = nuke.selectedNode()
if n['tile_color'].getValue()==0:
color_copied = nuke.defaultNodeColor(n.Class())
else:
color_copied = int(n['tile_color'].getValue())
else:
nuke.message("select a node")
if color_copied is None:
color_copied = 0
print "COPIED COLOR: " + str(color_copied)
#PASTE
def paste_color(self):
if color_copied is not None:
for n in nuke.selectedNodes():
n['tile_color'].setValue(color_copied)
#---------------------------------------------------------
#www
def website(self):
webbrowser.open('http://www.andreageremia.it/tutorial_color_panel.html') # Go to the website
#---------------------------------------------------------
#close window
def closeWindow(self):
if self.checkboxClose.isChecked():
self.close()
#GOOOOOOOOOOOOOOOOOO!
def colorPanel():
my_window = MyWindow()
my_window.show()
#colorPanel()
This code creates the Color Panel, but you have to have in the same folder also the file ui