Issue
I'm trying to add a custom Pyside widget to a Node in Nuke. Nuke allows this via PyCustom_Knob wrapper.
I am able to create the widget and display it, but it will not keep its values. Everytime I close the panel and reopen, it resets. How do I get it to keep its set value? What am I forgetting?
I am following this tutorial. (which has the same issue)
here is my current code:
from PySide import QtGui, QtCore
class myPyKnob(QtGui.QSpinBox):
def __init__(self, node):
super(self.__class__, self).__init__()
#Set a default value to the spinbox
self.setValue(1)
self.myValue = 0
self.valueChanged.connect(self.valueChangedKnob)
#Needed by Nuke to add the widget
def makeUI(self):
return self
def updateValue(self):
pass
def valueChangedKnob(self):
self.myValue = self.value()
print(self.myValue)
print(self.value())
# This takes the selected node and adds the widget using PyCustom_Knob
if __name__ == '__main__':
node = nuke.selectedNode()
knob = nuke.PyCustom_Knob( "MyWidget", "", "myPyKnob(nuke.thisNode())" )
node.addKnob(knob)
Here is a videolink to demo the issue:
Nuke Docs: PySide Widget at the very bottom
Thankyou
Solution
While Nuke knobs are persistent, PySide widgets created by the PyCustom_Knob do not exist when the panel or tab they are attached to are not open within Nuke. This means PySide widgets can not be interacted with before they are attached to a node / panel, or after that node / panel has been closed. I modified your original example to demonstrate:
from PySide import QtGui, QtCore
class myPyKnob(QtGui.QSpinBox):
def __init__(self, node):
super(self.__class__, self).__init__()
##########################
print("myPyKnob.__init__")
##########################
#Set a default value to the spinbox
self.setValue(1)
self.myValue = 0
self.valueChanged.connect(self.valueChangedKnob)
#Needed by Nuke to add the widget
def makeUI(self):
return self
def updateValue(self):
pass
def valueChangedKnob(self):
self.myValue = self.value()
print(self.myValue)
print(self.value())
# This takes the selected node and adds the widget using PyCustom_Knob
if __name__ == '__main__':
# This node is open in the properties panel
opened_node = nuke.toNode('opened_node')
pyside_knob1 = nuke.PyCustom_Knob( "MyWidget", "", "myPyKnob(nuke.toNode('opened_node'))" )
print("Before addKnob(): {}".format(pyside_knob1.getObject()))
opened_node.addKnob(pyside_knob1)
print("After addKnob(): {}".format(pyside_knob1.getObject()))
# This node is not open in the properties panel
unopened_node = nuke.toNode('unopened_node')
pyside_knob2 = nuke.PyCustom_Knob( "MyWidget", "", "myPyKnob(nuke.toNode('unopened_node'))" )
unopened_node.addKnob(pyside_knob2)
print("After addKnob(): {}".format(pyside_knob2.getObject()))
If you run this with the node 'opened_node' open in the properties editor and 'unopened_node' is not open in the properties editor, you will get the following output:
Before addKnob(): None
myPyKnob.__init__
After addKnob(): <__main__.myPyKnob object at 0x000000002DCC7588>
After addKnob(): None
For our opened node, the PySide object is not constructed until the knob is attached to node. For the unopened node, it is not created at all. As soon as you open 'unopened_node' in the properties panel, though, you will see the constructor go off.
This gets even more confusing after you close a node from the property panel. Close 'opened_node' from the properties panel and run this:
pyside_obj = nuke.toNode("opened_node").knob("MyWidget").getObject()
print(pyside_obj)
print(pyside_obj.value())
You should get output similar to this:
<__main__.myPyKnob object at 0x000000002DCC7588>
Traceback (most recent call last):
File "<string>", line 3, in <module>
RuntimeError: Internal C++ object (myPyKnob) already deleted.
At first, everything appears to be fine- the knob maintains a reference to the same object as before. However, if you try to run any internal methods, you will realize the internal object has been deleted! If you continue to close and reopen the node in the properties panel, you will see the constructor creates a new instance every time. This is obviously a huge problem, as not only do values not get saved, but nothing else in Nuke can retrieve those values if the node is not open.
The solution, which tk421storm already pointed out, is to store the values in another hidden knob on the node, as the knob will persist. This is pretty straightforward in your example, as an int_knob corresponds fairly well with a QSpinBox, but can get more complicated if your custom widget has more features. When I encountered this problem, my PySide widget was an entire panel with multiple drop down menus, checkboxes, and a dynamically sized list that users could grow or shrink depending on their needs. That is a lot of values that need to be propogated every time the user re-opens the node.
The solution I settled on was to store all of the PySide widget's values in a dictionary. Every callback for every widget updates calls an _updateCache() method (belonging to my pyside widget class) which encodes the values and stores them on an external String_Knob. The constructor then accepts this dictionary as an argument to restore its previous state.
def _updateCache(self):
"""
Updates a knob on the node to contain all of the values for this node.
Nuke will not store these values so we need to restore them ourselves after every
script load
"""
data = self._getInfo() # Gets all widget values in a dictionary
b64encode_data = base64.b64encode(json.dumps(data)) # Encode it to be safe
self.node.knob("pyside_cache").setValue(b64encode_data) # Store on an external String_Knob
Since every widget updates the cache instantly, external scripts never need to communicate directly with the PySide object, they just need to access the cache using a similar method to decode it:
def get_pyside_cache(self, node):
"""
Retrieve PySide Widget values
:return: dictionary of widget values, or None if failure
"""
if node.knob("pyside_cache").value() != "":
try:
return json.loads(base64.b64decode(node.knob("pyside_cache").value()))
except ValueError:
print("ValueError: Could not load a json object from pyside_cache")
return None
I hope this helps!
Answered By - caweeks
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.