2

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

4

2 回答 2

1

我很确定您必须想出自己的方法来将数据存储在 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)

在选择唯一名称时您可能必须更加聪明,因为如果用户在创建此旋钮后更改节点的名称,它将继续以其旧名称调用自己。

于 2018-03-22T15:53:45.057 回答
0

虽然 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

我希望这有帮助!

于 2019-02-10T21:05:54.167 回答