PyQt4/QProcess Nuke v9 问题

PyQt4/QProcess issues with Nuke v9

PyQt4/QProcess Nuke v9 问题...

我正尝试在我的工作场所使用 QProcess 运行 在 Nuke 中进行渲染。我想使用 QProcess 的原因是因为我在 Whosebug 社区的帮助下设置了这个任务管理器,它需要一个命令列表并按顺序 运行 一个一个地执行它,并且还允许我显示输出。您可以查看我在这里发布的问题:

现在我主要是想 运行 Nuke 通过这个 "Task Manager" 进行渲染。但是每次我这样做都会给我一个错误,即 QProcess 在 运行ning 时被破坏了。我的意思是我用 subprocess 测试了这个并且工作得很好。所以我不确定为什么渲染不能通过 QProcess 工作。

所以为了做更多的测试,我在家里写了一个简化版本。不过,我 运行 遇到的第一个问题是显然无法从 Nuke 的 python.exe 中找到 PyQt4。尽管我的主要 Python 版本有 PyQt4。但是显然我安装的 PyQt4 存在兼容性问题,因为我的主要 Python 版本是 2.7.12,而我的 Nuke 的 python 版本是 2.7.3。所以我想"fine then i'll just directly install PyQt4 inside my Nuke directory"。所以我抓住了这个 link 并将这个 PyQt 版本安装到我的 Nuke 目录中:

http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.10.3/PyQt4-4.10.3-gpl-Py2.7-Qt4.8.5-x64.exe

所以我 运行 我的小测试似乎在做与我工作场所相同的事情,QProcess 刚刚被销毁。所以我想也许添加 "waitForFinished()" 可能会做一些不同的事情,但它给了我这个错误,上面写着:

The procedure entry point ??4QString@@QEAAAEAV0@$$QEAV0@@Z could not be located in the dynamic link library QtCore4.dll

也给我这个错误:

ImportError: 无法加载 C:\Program Files\Nuke9.0v8\nuke-9.0.8.dll

现在我真的不能再在家里做任何测试了,我的工作室放假了。所以我有两个问题想请教:

1) 我看到的有关 "procedure entry point" 的错误是什么?只有当我尝试在 QProcess 实例中调用某些东西时才会发生。

2) 为什么我的 QProcess 在渲染完成之前就被销毁了??为什么子流程不会发生这种情况?如何提交 Nuke 渲染作业,同时获得与子进程相同的结果?

这是我的测试代码:

import os
import sys
import subprocess
import PyQt4
from PyQt4 import QtCore

class Task:
    def __init__(self, program, args=None):
        self._program = program
        self._args = args or []

    @property
    def program(self):
        return self._program

    @property
    def args(self):
        return self._args


class SequentialManager(QtCore.QObject):
    started = QtCore.pyqtSignal()
    finished = QtCore.pyqtSignal()
    progressChanged = QtCore.pyqtSignal(int)
    dataChanged = QtCore.pyqtSignal(str)
    #^ this is how we can send a signal and can declare what type
    # of information we want to pass with this signal

    def __init__(self, parent=None):
        # super(SequentialManager, self).__init__(parent)
        # QtCore.QObject.__init__(self,parent)
        QtCore.QObject.__init__(self)

        self._progress = 0
        self._tasks = []
        self._process = QtCore.QProcess(self)
        self._process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
        self._process.finished.connect(self._on_finished)
        self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)

    def execute(self, tasks):
        self._tasks = iter(tasks)
        #this 'iter()' method creates an iterator object
        self.started.emit()
        self._progress = 0
        self.progressChanged.emit(self._progress)
        self._execute_next()

    def _execute_next(self):
        try:
            task = next(self._tasks)
        except StopIteration:
            return False
        else:
            print 'starting %s' % task.args
            self._process.start(task.program, task.args)
            return True

    def _on_finished(self):
        self._process_task()
        if not self._execute_next():
            self.finished.emit()

    def _on_readyReadStandardOutput(self):
        output = self._process.readAllStandardOutput()
        result = output.data().decode()
        self.dataChanged.emit(result)

    def _process_task(self):
        self._progress += 1
        self.progressChanged.emit(self._progress)


class outputLog(QtCore.QObject):
    def __init__(self, parent=None, parentWindow=None):
        QtCore.QObject.__init__(self)
        self._manager = SequentialManager(self)

    def startProcess(self, tasks):
        # self._manager.progressChanged.connect(self._progressbar.setValue)
        self._manager.dataChanged.connect(self.on_dataChanged)
        self._manager.started.connect(self.on_started)
        self._manager.finished.connect(self.on_finished)
        self._manager.execute(tasks)

    @QtCore.pyqtSlot()
    def on_started(self):
        print 'process started'

    @QtCore.pyqtSlot()
    def on_finished(self):
        print 'finished'

    @QtCore.pyqtSlot(str)
    def on_dataChanged(self, message):
        if message:
            print message

def nukeTestRender():
    import nuke

    nuke.scriptOpen('D:/PC6/Documents/nukeTestRender/nukeTestRender.nk')

    writeNode = None
    for node in nuke.allNodes():
        if node.Class() == 'Write':
            writeNode = node

    framesList = [1, 20, 30, 40]
    fr = nuke.FrameRanges(framesList)
    # nuke.execute(writeNode, fr)

    for x in range(20):
        print 'random'

def run():
    nukePythonEXE = 'C:/Program Files/Nuke9.0v8/python.exe'
    thisFile = os.path.dirname(os.path.abspath("__file__"))
    print thisFile
    cmd = '"%s" %s renderCheck' %(nukePythonEXE, __file__)
    cmd2 = [__file__, 'renderCheck']
    cmdList = [Task(nukePythonEXE, cmd2)]
    # subprocess.call(cmd, stdin=None, stdout=None, stderr=None, shell=False)
    taskManager = outputLog()
    taskManager.startProcess(cmdList)
    taskManager._manager._process.waitForFinished()

if __name__ == "__main__":
    print sys.argv
    if len(sys.argv) == 1:
        run()
    elif len(sys.argv) == 2:
        nukeTestRender()

我已经想出了一个答案,所以我将详细写在下面:

基本上,我在安装 PyQt4 时遇到错误,因为它与我的 Nuke 版本不兼容,因此显然更推荐使用 Nuke 中包含的 PySide。但是 Nuke 的 Python 可执行文件本身无法找到 PySide,需要将一些路径添加到 sys.path:

paths = ['C:\Program Files\Nuke9.0v8\lib\site-packages,
C:\Users\Desktop02\.nuke',
'C:\Program Files\Nuke9.0v8\plugins',
'C:\Program Files\Nuke9.0v8\pythonextensions\site-packages\setuptools-0.6c11-py2.6.egg',
'C:\Program Files\Nuke9.0v8\pythonextensions\site-packages\protobuf-2.5.0-py2.6.egg',
'C:\Program Files\Nuke9.0v8\pythonextensions\site-packages',
'C:\Program Files\Nuke9.0v8\plugins\modules',
'C:\Program Files\Nuke9.0v8\configs\Python\site-packages',
'C:\Users\Desktop02\.nuke\Python\site-packages']

for path in paths:
   sys.path.append(path)

我通过在 GUI 模式下打开 Nuke 和 Python 可执行文件并比较两者 sys.path 以查看 Python 可执行文件缺少什么来找到丢失的路径。

然后回答我自己的主要问题:如果我在 QProcess 实例上调用 waitForFinished(-1),这将忽略此函数的默认 30 秒限制...答案来自此线程:

QProcess and shell : Destroyed while process is still running

所以这是我生成的工作代码:

import os
import sys
import subprocess
sysArgs = sys.argv
try:
    import nuke
    from PySide import QtCore
except ImportError:
    raise ImportError('nuke not currently importable')

class Task:
    def __init__(self, program, args=None):
        self._program = program
        self._args = args or []

    @property
    def program(self):
        return self._program

    @property
    def args(self):
        return self._args


class SequentialManager(QtCore.QObject):
    started = QtCore.Signal()
    finished = QtCore.Signal()
    progressChanged = QtCore.Signal(int)
    dataChanged = QtCore.Signal(str)
    #^ this is how we can send a signal and can declare what type
    # of information we want to pass with this signal

    def __init__(self, parent=None):
        # super(SequentialManager, self).__init__(parent)
        # QtCore.QObject.__init__(self,parent)
        QtCore.QObject.__init__(self)

        self._progress = 0
        self._tasks = []
        self._process = QtCore.QProcess(self)
        self._process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
        self._process.finished.connect(self._on_finished)
        self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)

    def execute(self, tasks):
        self._tasks = iter(tasks)
        #this 'iter()' method creates an iterator object
        self.started.emit()
        self._progress = 0
        self.progressChanged.emit(self._progress)
        self._execute_next()

    def _execute_next(self):
        try:
            task = next(self._tasks)
        except StopIteration:
            return False
        else:
            print 'starting %s' % task.args
            self._process.start(task.program, task.args)
            return True

    def _on_finished(self):
        self._process_task()
        if not self._execute_next():
            self.finished.emit()

    def _on_readyReadStandardOutput(self):
        output = self._process.readAllStandardOutput()
        result = output.data().decode()
        self.dataChanged.emit(result)

    def _process_task(self):
        self._progress += 1
        self.progressChanged.emit(self._progress)


class outputLog(QtCore.QObject):
    def __init__(self, parent=None, parentWindow=None):
        QtCore.QObject.__init__(self)
        self._manager = SequentialManager(self)

    def startProcess(self, tasks):
        # self._manager.progressChanged.connect(self._progressbar.setValue)
        self._manager.dataChanged.connect(self.on_dataChanged)
        self._manager.started.connect(self.on_started)
        self._manager.finished.connect(self.on_finished)
        self._manager.execute(tasks)

    @QtCore.Slot()
    def on_started(self):
        print 'process started'

    @QtCore.Slot()
    def on_finished(self):
        print 'finished'

    @QtCore.Slot(str)
    def on_dataChanged(self, message):
        if message:
            print message

def nukeTestRender():
    import nuke

    nuke.scriptOpen('D:/PC6/Documents/nukeTestRender/nukeTestRender.nk')

    writeNode = None
    for node in nuke.allNodes():
        if node.Class() == 'Write':
            writeNode = node

    framesList = [1, 20, 30, 40]
    fr = nuke.FrameRanges(framesList)
    nuke.execute(writeNode, fr)
    # nuke.execute(writeNode, start=1, end=285)

    for x in range(20):
        print 'random'

def run():
    nukePythonEXE = 'C:/Program Files/Nuke9.0v8/python.exe'
    thisFile = os.path.dirname(os.path.abspath("__file__"))
    print thisFile
    cmd = '"%s" %s renderCheck' %(nukePythonEXE, sysArgs[0])
    cmd2 = [sysArgs[0], 'renderCheck']
    cmdList = [Task(nukePythonEXE, cmd2)]
    # subprocess.call(cmd, stdin=None, stdout=None, stderr=None, shell=False)
    taskManager = outputLog()
    taskManager.startProcess(cmdList)
    taskManager._manager._process.waitForFinished(-1)

if __name__ == "__main__":
    print sys.argv
    if len(sysArgs) == 1:
        run()
    elif len(sysArgs) == 2:
        nukeTestRender()

无论出于何种原因,PySide 拒绝在没有先导入 nuke 模块的情况下为我加载。并且在导入 nuke 时还有一个已知错误,它会删除所有 sys.argv 参数,因此必须在 nuke 导入之前先将其存储在某个地方...