在场景中执行修改并从Maya中的QThread更新自定义窗口

2024-04-25 20:31:44 发布

您现在位置:Python中文网/ 问答频道 /正文

上下文

我正在创建一个在Maya中运行的PySide2工具。该工具正在执行许多长任务,一些修改场景(清理任务),一些创建文件(导出任务)

因为这是一项很长的任务,所以我希望在它运行时显示反馈(进度条)

问题

  • 不幸的是,到目前为止,整个UI在执行过程中似乎没有更新
  • 另外,因为我在真实代码中有奇怪的行为(Maya永远冻结),我猜这不是线程的安全使用

示例代码

下面是一段简化的代码,显示了我到目前为止所处的位置。这是使用QThread的正确方法吗?我来自CG艺术家背景,不是专业程序员,所以我可能误用或误解了我试图使用的概念(线程、PySide…)

import time

from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtWidgets import *

import maya.cmds as cmds


class Application(object):
    def __init__(self):
        self.view = View(self)

    def do_something(self, callback):
        start = int(cmds.playbackOptions(q=True, min=True))
        end = int(cmds.playbackOptions(q=True, max=True))

        # First operation
        for frame in xrange(start, end + 1):
            cmds.currentTime(frame, edit=True)
            # Export ...
        callback(33)
        time.sleep(1)

        # Second operation
        for frame in xrange(start, end + 1):
            cmds.currentTime(frame, edit=True)
            # Export ...
        callback(66)
        time.sleep(1)

        # Third operation
        for frame in xrange(start, end + 1):
            cmds.currentTime(frame, edit=True)
            # Export ...
        callback(100)
        time.sleep(1)


class View(QWidget):
    def __init__(self, controller):
        super(View, self).__init__()
        self.controller = controller
        self.thread = None
        
        self.setLayout(QVBoxLayout())
        
        self.progress = QLabel()
        self.layout().addWidget(self.progress)

        self.button = QPushButton('Do something')
        self.layout().addWidget(self.button)
        
        self.button.clicked.connect(self.do_something)
        
        self.show()
        
    def do_something(self):
        self.thread = DoSomethingThread(self.controller)
        self.thread.updated.connect(lambda progress: self.progress.setText(str(progress) + '%'))
        self.thread.run()
    
    
class DoSomethingThread(QThread):
    completed = Signal()
    updated = Signal(int)

    def __init__(self, controller, parent=None):
        super(DoSomethingThread, self).__init__(parent)
        self.controller = controller

    def run(self):
        self.controller.do_something(self.update_progress)
        self.completed.emit()
        
    def update_progress(self, progress):
        self.updated.emit(int(progress))
        
app = Application()

Tags: importselftruetimeinitdefcallbackdo
1条回答
网友
1楼 · 发布于 2024-04-25 20:31:44

在Maya Python中很难正确使用线程(从列出的问题数here可以看出这一点)

通常有两条严格的规则需要遵守:

  1. 所有涉及Maya场景的工作(例如选择或移动对象)都必须在主线程中进行
  2. 所有涉及Maya GUI的工作也必须在主线程中进行

"main thread" here is the thread you get when you run a script from the listener, not on you're creating for yourself

这显然让很多事情很难做。通常,解决方案将涉及在主线程上运行的控制操作,而其他不涉及Maya GUI或场景对象的工作正在其他地方进行。线程安全容器(如pythonQueue)可用于将已完成的工作从工作线程转移到主线程可以安全到达的位置,或者您可以使用QT信号安全地触发主线程中的工作……如果您的编程生涯进展不远,所有这些都有点棘手

好消息是,如果您希望在Maya中执行的所有工作都在场景中,那么您不会因为没有线程而损失太多。除非这些工作基本上是非Maya工作,例如使用HTTP请求获取web数据,或者将非Maya文件写入磁盘,或者其他不处理特定于Maya的数据的工作,否则添加线程将无法获得u任何其他性能。看起来您的示例正在推进时间线,进行工作,然后尝试更新PySide GUI。为此,您根本不需要线程(您也不需要单独的QApplication Maya已经是QApplication)

这里有一个非常愚蠢的例子

from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
import maya.cmds as cmds

class DumbWindow(QWidget):

    def __init__(self):
        super(DumbWindow, self).__init__()
        
        #get the maya app
        maya_app = QCoreApplication.instance()
        
        # find the main window for a parent
        for widget in maya_app.topLevelWidgets():
            if 'TmainWindow' in widget.metaObject().className():
                self.setParent(widget)
                break
                
        self.setWindowTitle("Hello World")
        self.setWindowFlags(Qt.Window)
        
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        
        start_button = QPushButton('Start', self)
        stop_button = QPushButton('Stop', self)
        self.layout.addWidget(start_button)
        self.layout.addWidget(stop_button)
        
        self.should_cancel = False
        self.operation = None
        self.job = None

        # hook up the buttons
        start_button.clicked.connect(self.start)
        stop_button.clicked.connect(self.stop)


    def start(self):
        '''kicks off the work in 'this_is_the_work''' 
        self.operation = self.this_is_the_work()
        self.should_cancel = False
        self.job = cmds.scriptJob(ie=self.this_makes_it_tick)
            
        
    def stop(self):
        ''' cancel before the next step'''
        self.should_cancel = True

    def this_is_the_work(self):
        print " - started  -"        
        for frame in range(100):
            cmds.currentTime(frame, edit=True)
            yield "advanced", frame
        
        print " - DONE   "

    def bail(self):
        self.operation = None
        def kill_my_job():
            cmds.scriptJob(k=self.job)
            print "job killed"
        
        cmds.scriptJob(ie = kill_my_job, runOnce=True)

    def this_makes_it_tick(self):
        '''
        this is called whenever Maya is idle and thie
        '''

        # not started yet
        if not self.operation:
            return

        # user asked to cancel
        if self.should_cancel:
            print "cancelling"
            self.bail()
            return            

        try:
            # do one step.  Here's where you can update the 
            # gui if you need to 
            result =   next(self.operation)
            print result
            # example GUI update
            self.setWindowTitle("frame %i" % result[-1])
        except StopIteration:
            # no more stpes, we're done
            print "completed"
            self.bail()
        except Exception as e:
            print "oops", e
            self.bail()
 
         

test = DumbWindow()
test.show()

点击start会创建一个maya scriptJob,它将尝试运行名为this_is_the_work()的函数中的任何操作。它将运行到下一个yield语句,然后检查以确保用户没有要求取消该作业。在这两个操作之间,maya将很忙(就像在侦听器中输入一些行一样)但是,如果在出现屈服时与Maya交互,脚本将等待您。这允许在没有单独线程的情况下进行安全的用户交互,当然也不像完全单独的线程那样平滑

您会注意到,这将启动bail()方法中的第二个scriptJob,这是因为scriptJob无法自行终止,因此我们创建了另一个scriptJob,它将在下一个空闲事件期间运行,并终止我们不想要的scriptJob

这个技巧基本上就是大多数Maya基于MEL的UI在后台工作的方式。如果在侦听器中运行cmds.scriptJob(lj=True),您通常会看到许多表示UI元素的ScriptJob,这些UI元素跟踪事情

相关问题 更多 >