I want to add a gif element in my custom Maya UI using python. Since Maya doens't accept animated images as input I created a while loop and edit the input image. To do to that I used Timer and everything works fine, untill I rerun the script. The old thread is somehow still active (altough I kill it every time I run the script)
This is the code I have. I have a start and stop button that work exactly as they are supposed to, but if I rerun the script, the old thread is still active.
from threading import Timer
import time
import maya.cmds as cmds
global t
global N
global B
def initGif():
global t
global N
global B
terminateGif()
N = 0
t = Timer(1.0, startGif)
t.start()
def startGif():
global N
global t
while N < 1000:
if N < 1000:
print "Hello", t # This is where the actual images would iterate instead of "Hello"
time.sleep(5)
else:
terminateGif()
continue
def terminateGif():
global t
global N
N = 9999999
try:
t.cancel()
except:
t = "None"
return
def UI():
if cmds.window("win", exists = True):
cmds.deleteUI("win")
cmds.window("win", w = 500, h = 500)
cmds.columnLayout()
cmds.button("stop", c = lambda *args : terminateGif())
cmds.button("start", c = lambda *args : initGif())
cmds.showWindow("win")
UI()
initGif()
The fact that you're trying to get a gif working with threading and a timer is just asking for Maya to crash, or at least slow down the scene's performance.
Instead of bearing all of the overhead, I strongly recommend you just use PySide, which is built-in to Maya anyways. Here's a simple example without having to deal with the nightmare that is threading:
from PySide2 import QtCore
from PySide2 import QtGui
from PySide2 import QtWidgets
class Win(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Win, self).__init__(parent)
self.setWindowTitle("Gif Example")
self.resize(500, 500)
self.movie = QtGui.QMovie("/PATH/TO/YOUR/GIF.gif") # Set your gif path here.
self.movie.setScaledSize(QtCore.QSize(150, 150)) # You can resize it too.
self.movie.start()
self.gif = QtWidgets.QLabel(parent=self) # Use QLabel to display the gif.
self.gif.setMovie(self.movie)
self.main_layout = QtWidgets.QVBoxLayout()
self.main_layout.addWidget(self.gif)
self.setLayout(self.main_layout)
win = Win()
win.show()
Related
I am using this simple code to show warning boxes:
w = QWidget()
result = QMessageBox.warning(w, 'a', x, QMessageBox.Ok)
Is there any way to change MESSAGE dynamically? I want to make a popup which will inform user abut progress of a task that is running in background.
Edit:
Well I tried to do so making this script for testing:
def handleButton(self):
self.msgBox = QMessageBox(self)
self.msgBox.setWindowTitle("Title")
self.msgBox.setIcon(QMessageBox.Warning)
self.msgBox.setText("Start")
self.msgBox.show()
x = 0
for x in range (100):
x = x + 1
print (x)
self.msgBox.setText(str(x))
self.msgBox.show()
time.sleep(1)
The text only shows after finishing the 'for loop', why?
Instead of using a static method you could create an object of the class.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Widget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.msgBox = QMessageBox(self)
self.msgBox.setWindowTitle("Title")
self.msgBox.setIcon(QMessageBox.Warning)
self.msgBox.setText("Start")
self.msgBox.show()
timer = QTimer(self)
timer.timeout.connect(self.onTimeout)
timer.start(1000)
def onTimeout(self):
self.msgBox.setText("datetime: {}".format(QDateTime.currentDateTime().toString()))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Update:
The problem in your example is the use of time.sleep(). Qt is executed in an eventloop, this eventloop allows you to handle the events of the mouse, keyboard, redraw, etc. but the time.sleep() blocks the eventloop, this you can check trying to change the size of the window, you will see that you can not do it.
Assuming you use time.sleep() to pause, then you must use QEventLoop with QTimer that does not block the Qt eventloop.
def handleButton(self):
self.msgBox = QMessageBox(self)
self.msgBox.setWindowTitle("Title")
self.msgBox.setIcon(QMessageBox.Warning)
self.msgBox.setText("Start")
self.msgBox.show()
for x in range(100):
self.msgBox.setText(str(x+1))
loop = QEventLoop()
QTimer.singleShot(1000, loop.quit)
loop.exec_()
I am trying to load some data which takes 30+ seconds. During this time I wish the user to see a small GUI which says "Loading .", then "Loading ..", then "Loading ...", then "Loading ." etc. I have done some reading and I think I have to put this in a separate thread. I found someone who had a similar problem suggesting the solution was this in the right spot:
t = threading.Thread(target=self.test)
t.daemon = True
t.start()
In a lower part of the file I have the test function
def test(self):
tmp = InfoMessage()
while True:
print(1)
and the InfoMessage function
from PyQt5 import uic, QtCore, QtGui, QtWidgets
import sys
class InfoMessage(QtWidgets.QDialog):
def __init__(self, msg='Loading ', parent=None):
try:
super(InfoMessage, self).__init__(parent)
uic.loadUi('ui files/InfoMessage.ui',self)
self.setWindowTitle(' ')
self.o_msg = msg
self.msg = msg
self.info_label.setText(msg)
self.val = 0
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_message)
self.timer.start()
self.show()
except BaseException as e:
print(str(e))
def update_message(self):
self.val += 1
self.msg += '.'
if self.val < 20:
self.info_label.setText(self.msg)
else:
self.val = 0
self.msg = self.o_msg
QtWidgets.QApplication.processEvents()
def main():
app = QtWidgets.QApplication(sys.argv) # A new instance of QApplication
form = InfoMessage('Loading ') # We set the form to be our MainWindow (design)
app.exec_() # and execute the app
if __name__ == '__main__': # if we're running file directly and not importing it
main() # run the main function
When I run the InfoMessage function alone it works fine and it updates every 0.5 seconds etc. However, when I fun this as part of the loading file the GUI is blank and incorrectly displayed. I know it is staying in the test function because of the print statement in there.
Can someone point me in the right direction? I think I am missing a couple of steps.
First, there are two ways of doing this. One way is to use the Python builtin threading module. The other way is to use the QThread library which is much more integrated with PyQT. Normally, I would recommend using QThread to do threading in PyQt. But QThread is only needed when there is any interaction with PyQt.
Second, I've removed processEvents() from InfoMessage because it does not serve any purpose in your particular case.
Finally, setting your thread as daemon implies your thread will never stop. This is not the case for most functions.
import sys
import threading
import time
from PyQt5 import uic, QtCore, QtWidgets
from PyQt5.QtCore import QThread
def long_task(limit=None, callback=None):
"""
Any long running task that does not interact with the GUI.
For instance, external libraries, opening files etc..
"""
for i in range(limit):
time.sleep(1)
print(i)
if callback is not None:
callback.loading_stop()
class LongRunning(QThread):
"""
This class is not required if you're using the builtin
version of threading.
"""
def __init__(self, limit):
super().__init__()
self.limit = limit
def run(self):
"""This overrides a default run function."""
long_task(self.limit)
class InfoMessage(QtWidgets.QDialog):
def __init__(self, msg='Loading ', parent=None):
super(InfoMessage, self).__init__(parent)
uic.loadUi('loading.ui', self)
# Initialize Values
self.o_msg = msg
self.msg = msg
self.val = 0
self.info_label.setText(msg)
self.show()
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_message)
self.timer.start()
def update_message(self):
self.val += 1
self.msg += '.'
if self.val < 20:
self.info_label.setText(self.msg)
else:
self.val = 0
self.msg = self.o_msg
def loading_stop(self):
self.timer.stop()
self.info_label.setText("Done")
class MainDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
# QThread Version - Safe to use
self.my_thread = LongRunning(limit=10)
self.my_thread.start()
self.my_loader = InfoMessage('Loading ')
self.my_thread.finished.connect(self.my_loader.loading_stop)
# Builtin Threading - Blocking - Do not use
# self.my_thread = threading.Thread(
# target=long_task,
# kwargs={'limit': 10}
# )
# self.my_thread.start()
# self.my_loader = InfoMessage('Loading ')
# self.my_thread.join() # Code blocks here
# self.my_loader.loading_stop()
# Builtin Threading - Callback - Use with caution
# self.my_loader = InfoMessage('Loading ')
# self.my_thread = threading.Thread(
# target=long_task,
# kwargs={'limit': 10,
# 'callback': self.my_loader}
# )
# self.my_thread.start()
def main():
app = QtWidgets.QApplication(sys.argv)
dialog = MainDialog()
app.exec_()
if __name__ == '__main__':
main()
Feel free to ask any follow up questions regarding this code.
Good Luck.
Edit:
Updated to show how to run code on thread completion. Notice the new parameter added to long_task function.
I am trying to modify this piece of code by calling out a import window then follow by this code.
As I am also going to use the current piece of code (which is not written by me), the way it works is that when user selects one of the 3 prefixes ['a','b','c'], it will change the naming of the items in Maya accordingly.
Part of the former Coding (prefix window):
import maya.cmds as cmds
import maya.mel as mel
import pymel.core as pm
from PyQt4 import QtGui, QtCore
import sys, os
class createUI(QtGui.QFrame):
def __init__(self, parent=None):
QtGui.QFrame.__init__(self, parent)
self.shot = SHOT
self.initUI()
self.connections()
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.resize(400,100)
self.move(300,300)
self.setWindowTitle('Select the prefix of rexPass')
self.pubDock = createUI()
vLayout = QtGui.QVBoxLayout()
vLayout.addWidget(self.pubDock)
self.setLayout(vLayout)
self.createConnection()
def createConnection(self):
self.connect( self.pubDock.cancelButton, QtCore.SIGNAL( "clicked()" ), self.close )
self.connect( self.pubDock.OKButton, QtCore.SIGNAL( "clicked()" ), self.close )
def setupRenderGlobals():
cmds.setAttr ('StartRange.multiFrame', 1)
cmds.setAttr ('EndRange.endFrame', 200)
cmds.setAttr ('WidthRes.xres', 1920)
cmds.setAttr ('HeightRes.yres', 1080)
def main():
setupRenderGlobals()
global app
app=QtGui.qApp
global form
form = MainWindow()
form.show()
Currently I would like to add on a function where it calls a selection window to import something, and once the selection is done, it will then calls out the above code.
The problem I have is where when user hits the import button in the import window, it automatically closes, and the perfix window is not showing up, or I would have the 2 windows showing up or just the prefix window and not the import window
My Coding:
class Processing():
'In-house code to call out the import window and they will have the name of 'prItems_a01''
importItems = procureItems.api.importItem()
allItems = pm.ls(type="prItems")
if allItem < 2 :
test = MainWindow()
else:
print ('testing')
Any advices?
The problem is here:
if allItem < 2 :
test = MainWindow()
else:
print ('testing')
allItems = pm.ls(type="prItems")
if allItem < 2 :
test = MainWindow()
pymel.core.ls returns a list, while 2 is an int. Python may not do what you expect here. From the docs:
Objects of different types except numbers are ordered by their type names; objects of the same types that don’t support proper comparison are ordered by their address.
So, "list" > "int"
What you probably meant to do is check the len of allItem, like this:
def processing():
# ~~ your code ~~ #
if len(allItem) < 2:
test = MainWindow()
else:
print ('testing')
I have a problem with PyQT4 for Python. There is a label with text and button connected to function. The function could change the text of label first, then call other function. There is a problem with it: the function is executed firts, then change text of the label.
Code:
# -*- coding: utf-8 -*-
import time
import sys
from PyQt4 import QtCore, QtGui
def timesleep():
print("start sleep")
time.sleep(5)
print("stop sleep")
class AnyWidget(QtGui.QWidget):
def __init__(self,*args):
QtGui.QWidget.__init__(self,*args)
self.setWindowTitle("PETHARD")
boxlay = QtGui.QHBoxLayout(self)
frame = QtGui.QFrame(self) # Фрейм
frame.setFrameShape(QtGui.QFrame.StyledPanel)
frame.setFrameShadow(QtGui.QFrame.Raised)
gridlay = QtGui.QGridLayout(frame) # Менеджер размещения элементов во фрейме
label = QtGui.QLabel(u"Welcome",frame) # Текстовая метка.
global glabel
glabel = label
gridlay.addWidget(label,0,0)
button1 = QtGui.QPushButton(u"Load From MC", frame)
self.connect(button1, QtCore.SIGNAL("clicked()"), self.ts)
gridlay.addWidget(button1,1,0)
boxlay.addWidget(frame)
def ts(self):
global glabel
glabel.setText(u"Waiting...")
timesleep()
if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
aw = AnyWidget()
aw.show()
sys.exit(app.exec_())
Help me please to fix this problem.
It work's like that because rendering is done later in app. So your glabel.text is changed immediately but you will see changed text on screen after your ts function call, because drawing is done at the end of loop.
If you really want to call your function in new frame (after rendering of new text) then use timer:
timer = QtCore.QTimer()
QtCore.QObject.connect(
timer,
QtCore.SIGNAL("timeout()"),
self.ts
)
timer.start(10)
It should call your function ten milisecond later, so in fact probably after rendering.
You never want to tie-up your GUI with a long-running function. That the label does not update is only one manifestation of the problem. Even if you get the label to update before the function is called, it will still "freeze" your GUI -- no widget will respond to the user until the long-running function completes.
If you have to run such a function, see if there is a way to break it up into small pieces which each relinquish control to the GUI, or consider using a separate thread to run the function:
import time
import sys
from PyQt4 import QtCore, QtGui
class TimeSleep(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
self.parent = parent
def run(self):
print("start sleep")
time.sleep(5)
print("stop sleep")
self.parent.glabel.setText(u"Done")
class AnyWidget(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
self.setWindowTitle("PETHARD")
boxlay = QtGui.QHBoxLayout(self)
frame = QtGui.QFrame(self) # Фрейм
frame.setFrameShape(QtGui.QFrame.StyledPanel)
frame.setFrameShadow(QtGui.QFrame.Raised)
gridlay = QtGui.QGridLayout(
frame) # Менеджер размещения элементов во фрейме
label = QtGui.QLabel(u"Welcome", frame) # Текстовая метка.
self.glabel = label
gridlay.addWidget(label, 0, 0)
button1 = QtGui.QPushButton(u"Load From MC", frame)
self.connect(button1, QtCore.SIGNAL("clicked()"), self.ts)
gridlay.addWidget(button1, 1, 0)
boxlay.addWidget(frame)
def ts(self):
self.glabel.setText(u"Waiting...")
self.thread = TimeSleep(parent=self)
self.thread.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
aw = AnyWidget()
aw.show()
sys.exit(app.exec_())
Here is a wiki page on how to deal with long-running functions.
The text of the label is being changed, but because you're blocking the main thread you're not giving Qt a chance to paint it.
Use QCoreApplication::processEvents and QCoreApplication::flush:
def ts(self):
glabel.setText(u"Waiting...")
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.flush()
timesleep()
I'm trying to make basic functionality
after pressing "start" button start counter , after pressing stop button stop counter,
but after I start process, it looks like only counting thread is working and it's not possible to press stop button
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
from test.test_sax import start
import time
from threading import Thread
import threading
class Example(QtGui.QWidget):
x = 1
bol = True
def __init__(self):
super(Example, self).__init__()
self.qbtn = QtGui.QPushButton('Quit', self)
self.qbtn.resize(self.qbtn.sizeHint())
self.qbtn.move(50, 50)
self.qbtn2 = QtGui.QPushButton('Start', self)
self.qbtn2.resize(self.qbtn2.sizeHint())
self.qbtn2.move(150, 50)
self.qbtn.clicked.connect(self.stopCounter)
self.qbtn2.clicked.connect(self.startUI)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Quit button')
self.show()
def stopCounter(self):
Example.bol = False
def startUI(self):
Example.bol = True
thread = Thread(self.counterr())
def counterr(self):
x = 0
while Example.bol:
print x
x += 1
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
a = Example()
sys.exit(app.exec_())
thx
Now you call the slow function before you even create the thread. Try this instead:
thread = Thread(target=self.counterr)
thread.start()
In a Qt application you might also consider the QThread class that can run its own event loop and communicate with your main thread using signals and slots.
You are using the Thread class completely incorrectly, I'm afraid. You are passing it the result of the counterr method, which never returns.
Pass counterr (without calling it) to the Thread class as the target, then start it explicitly:
def startUI(self):
self.bol = True
thread = Thread(target=self.counterr)
thread.start()
Also, just access bol as an instance variable, not a class variable.