Nuke的Pyside Widget没有保持价值观

时间:2018-01-06 22:56:23

标签: python pyside nuke

我正在尝试将自定义Pyside小部件添加到Nuke中的Node。 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

三江源

2 个答案:

答案 0 :(得分: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)

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

答案 1 :(得分: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

我希望这会有所帮助!