虽然 Nuke 旋钮是持久的,但当它们附加到的面板或选项卡未在 Nuke 中打开时,由 PyCustom_Knob 创建的 PySide 小部件不存在。这意味着 PySide 小部件在附加到节点/面板之前或在该节点/面板关闭之后无法交互。我修改了你原来的例子来演示:
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()))
如果您在属性编辑器中打开节点“opened_node”并在属性编辑器中未打开“unopened_node”运行此程序,您将获得以下输出:
Before addKnob(): None
myPyKnob.__init__
After addKnob(): <__main__.myPyKnob object at 0x000000002DCC7588>
After addKnob(): None
对于我们打开的节点,PySide 对象在旋钮连接到节点之前不会被构造。对于未打开的节点,根本不会创建它。但是,一旦您在属性面板中打开“unopened_node”,您就会看到构造函数关闭。
从属性面板关闭节点后,这会变得更加混乱。从属性面板关闭“opened_node”并运行:
pyside_obj = nuke.toNode("opened_node").knob("MyWidget").getObject()
print(pyside_obj)
print(pyside_obj.value())
你应该得到类似这样的输出:
<__main__.myPyKnob object at 0x000000002DCC7588>
Traceback (most recent call last):
File "<string>", line 3, in <module>
RuntimeError: Internal C++ object (myPyKnob) already deleted.
起初,一切似乎都很好——旋钮保持对与以前相同的对象的引用。但是,如果您尝试运行任何内部方法,您将意识到内部对象已被删除!如果您继续在属性面板中关闭并重新打开节点,您将看到构造函数每次都创建一个新实例。这显然是一个大问题,因为不仅值不会被保存,而且如果节点未打开,Nuke 中的其他任何东西都无法检索这些值。
tk421storm 已经指出的解决方案是将值存储在节点上的另一个隐藏旋钮中,因为旋钮将持续存在。这在您的示例中非常简单,因为 int_knob 与 QSpinBox 对应得相当好,但如果您的自定义小部件具有更多功能,则可能会变得更加复杂。当我遇到这个问题时,我的 PySide 小部件是一个完整的面板,其中包含多个下拉菜单、复选框和一个动态大小的列表,用户可以根据自己的需要进行扩展或缩小。每次用户重新打开节点时,都需要传播很多值。
我确定的解决方案是将所有 PySide 小部件的值存储在字典中。每个小部件更新的每个回调都会调用一个 _updateCache() 方法(属于我的 pyside 小部件类),该方法对值进行编码并将它们存储在外部 String_Knob 上。然后构造函数接受这个字典作为参数来恢复它之前的状态。
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
由于每个小部件都会立即更新缓存,因此外部脚本永远不需要直接与 PySide 对象通信,它们只需要使用类似的方法访问缓存即可对其进行解码:
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
我希望这有帮助!