Nuke 中的 Pyside 小部件不保持值

Pyside Widget in Nuke not Keeping Values

我正在尝试将自定义 Pyside 小部件添加到 Nuke 中的节点。 Nuke 通过 PyCustom_Knob 包装器允许这样做。

我可以创建小部件并显示它,但它不会保留其值。 每次我关闭面板并重新打开时,它都会重置。我如何让它保持其设定值?我忘记了什么?

我正在学习 this 教程。 (有同样的问题)

这是我当前的代码:

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 是演示问题的视频链接:

Nuke Docs: PySide Widget 在最底部

谢谢

我很确定你必须想出自己的方法来将数据存储在 Py_custom 旋钮上 - 听起来 nuke 默认情况下不会这样做。

您可以像我经常做的那样将您的数据存储在脚本 Root() 的隐藏旋钮上。您的代码可能如下所示:

from PySide import QtGui, QtCore

class myPyKnob(QtGui.QSpinBox):

    def __init__(self, node):
        super(self.__class__, self).__init__()

        #each knob will need a custom name
        self.name=node.name()+"_pyCustomKnob"

        #we'll check for a stored result at the root and set it here
        #this should work when a script is loaded as well
        try:
             value=nuke.Root().toKnob(self.name).value()
        except:
             value=1
        self.setValue(value)

        self.valueChanged.connect(self.valueChangedKnob)

    #Needed by Nuke to add the widget
    def makeUI(self):
        return self

    def updateValue(self):
        pass

    def valueChangedKnob(self):
        myValue = self.value()
        #store the current result on the root
        try:
            nuke.Root().toKnob(self.name).setValue(myValue)
        except KeyError:
            #knob doesnt exist so we need to make it
            storageKnob=nuke.Int_Knob(self.name)
            nuke.Root().addKnob(storageKnob)
            storageKnob.setVisible(False)
            storageKnob.setValue(myValue)

# 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)

您可能必须 clever-er 选择唯一名称,因为如果用户在创建此旋钮后更改节点名称,它将继续使用其旧名称调用自己.

虽然 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 小部件 class),该方法对值进行编码并将它们存储在外部 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

希望对您有所帮助!