PyQt animate QGraphicsItem not working - python-2.7

I'm using Python 2.7 with PyQt 4.0.
I'm trying to make a QGraphicsRectItem move 10 px up in a animation. I have read the documentation and several tutorials but I can't get it to work. What is wrong with my code?
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import random
class TestWidget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.scene = QGraphicsScene()
self.view = QGraphicsView(self.scene)
self.button1 = QPushButton("Do test")
self.button2 = QPushButton("Move forward 10")
layout = QVBoxLayout()
buttonLayout = QHBoxLayout()
buttonLayout.addWidget(self.button1)
buttonLayout.addWidget(self.button2)
buttonLayout.addStretch()
layout.addWidget(self.view)
layout.addLayout(buttonLayout)
self.setLayout(layout)
self.button1.clicked.connect(self.do_test)
self.button2.clicked.connect(self.move_forward)
def do_test(self):
self.turtle = self.scene.addRect(0,0,10,20)
def move_forward(self):
animation = QGraphicsItemAnimation()
timeline = QTimeLine(1000)
timeline.setFrameRange(0,100)
animation.setTimeLine(timeline)
animation.setItem(self.turtle)
animation.setPosAt(1.0, QPointF(self.turtle.x(),self.turtle.y()+10))
timeline.start()
Thanks for the help!

The reason why your example doesn't work, is that you are not keeping a reference to the QGraphicsItemAnimation created in the move_forward method, and so it gets garbage-collected before it has a chance to do anything.
I would suggest you create the animation in __init__ so that you can access it later as an instance attribute:
def __init__(self, parent=None):
...
self.animation = QGraphicsItemAnimation()
def move_forward(self):
timeline = QTimeLine(1000)
timeline.setFrameRange(0, 100)
self.animation.setTimeLine(timeline)
self.animation.setItem(self.turtle)
self.animation.setPosAt(
1.0, QPointF(self.turtle.x(), self.turtle.y() + 10))
timeline.start()

try this small change (in function move_forward).
replace
animation = QGraphicsItemAnimation()
with
animation = QGraphicsItemAnimation(self)
that changes the behaviour for me.

Related

Modify model data from threads in in PyQt

I'm having trouble when modifying the source model of a QSortFilterProxyModel from another thread, while the proxy model is connected to a view. The proxy model seems to add "ghost" items to the model being displayed, blank items that cannot be interacted with (apparently)
This example code demonstrates the problem I'm experiencing, reduced down to a bare-basic example where there's a view for each of the proxy model and the source model:
from PySide2 import QtCore, QtGui, QtWidgets
import random
WordList = ["apples", "pears", "down", "stairs", "mother", "hubord", "isn't", "cupboard", "lemon", "tango", "apricot", "nuke"]
class Searcher(QtCore.QThread):
def __init__(self, model):
super(Searcher, self).__init__()
self.model = model
def run(self):
populateModel(self.model)
def populateModel(model):
for i in range(5):
item1 = QtGui.QStandardItem()
model.invisibleRootItem().appendRow(item1)
majorWord = random.choice(WordList)
item1.setData(majorWord, QtCore.Qt.DisplayRole)
for i in range(random.randint(2, 8)):
item2 = QtGui.QStandardItem()
item1.appendRow(item2)
item2.setData(os.path.join(majorWord, random.choice(WordList)), QtCore.Qt.DisplayRole)
if __name__ == '__main__':
tv = QtWidgets.QTreeView()
tv.setWindowTitle("Source Model, Threaded")
tv.show()
tv2 = QtWidgets.QTreeView()
tv2.setWindowTitle("Proxy Model, Threaded")
tv2.show()
tv3 = QtWidgets.QTreeView()
tv3.setWindowTitle("Proxy Model 2, No Threading")
tv3.show()
sourceModel = QtGui.QStandardItemModel()
tv.setModel(sourceModel)
proxyModel = QtCore.QSortFilterProxyModel()
proxyModel.setDynamicSortFilter(True)
proxyModel.setSourceModel(sourceModel)
tv2.setModel(proxyModel)
s = Searcher(sourceModel)
s.start()
sm3 = QtGui.QStandardItemModel()
pm3 = QtCore.QSortFilterProxyModel()
pm3.setSourceModel(sm3)
proxyModel.setDynamicSortFilter(True)
tv3.setModel(pm3)
populateModel(sm3)
I thought that maybe the setDynamicSortFilter property of the proxy model could be useful, but it didn't seem to have any effect.
Any help or pointers appreciated!

PyQt4 label crashes when a function is called instead of using raw_input?

I boiled the original code down to a small section that still reproduces the issue. The below code works fine with action = raw_input('next action? ') instead of action = self.fake(). WHY??!! Specifically, the 'label' window will hang and crash using the class function, but will display the two overlaid images no problem using the user input. I cannot fathom how the two are impacting PyQt, especially since the changes are being made AFTER the image update.
import time
import sys
from PyQt4 import QtGui
class Basement(object):
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
self.label = QtGui.QLabel()
def update_image(self):
self.im = QtGui.QImage('n-wall.png')
painter = QtGui.QPainter()
c_image = QtGui.QImage('bed.png')
painter.begin(self.im)
painter.drawImage(10, 10, c_image)
painter.end()
self.label.setPixmap(QtGui.QPixmap.fromImage(self.im))
self.label.show()
def fake(self):
return 'left'
def play_game(self):
### Update graphics / text
self.update_image()
### Decide action
action = self.fake()
#action = raw_input('next action? ')
time.sleep(5)
B = Basement()
B.play_game()

How to create activity indicator using pyQt4 designer for python

I'm learning about GUI python using pyQt4. I have function A in another file python. and I want to run in GUI file python that I extracted from file .ui (output of designer pyQt4). How to create activity indicator which is active when the function A is running? can I use progress bar (in pyQt4 designer) without know how many time for my function A running?
Thank you.
this is the function to call A in GUI .py:
def RunFunction():
import Kdtree
_dir = kdTreeOk.getNeighbor(float(radius)) #function 'A'
file = file_open('Summary.txt',_dir) # ignore, just file to save result of `A`
with file:
textOutput=file.read()
ui.result.setPlainText(textOutput)
#### button to run RunFunction in file GUI .py
ui._run.clicked.connect(RunFunction)
QProgressDialog is made for this purpose and generally called via QThread. Here's a (messy) basic example to show how this can work (without any threading). If you are calling this dialog from another window, just set parent as the calling window and you can read attributes in this dialog by calling self.parent.some_variable.
EDITED to work properly ;).
from PyQt4 import QtCore, QtGui
from time import sleep
import sys
class ProgressBarWidget(QtGui.QProgressDialog):
def __init__(self, parent=None, app=None):
super(ProgressBarWidget, self).__init__(parent)
self.app=app
self._allow_close = True
layout = QtGui.QVBoxLayout(self)
# Create a progress bar and a button and add them to the main layout
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setRange(0,100)
layout.addWidget(self.progressBar)
self.button = QtGui.QPushButton("Start", self)
layout.addWidget(self.button)
self.button.clicked.connect(self.onStart)
self.upload_count = 10
def onStart(self):
self.progressBar.setValue(0)
self.button.setText("Uploading...")
self.run()
def makeProgress(self, current_num, total_num, message = ''):
if total_num == current_num:
self.onFinished()
elif current_num == 0:
self.progressBar.setValue(0)
else:
multiplier = int(float(float(100) / float(total_num)))
c_times_m = current_num * multiplier
for i in xrange(c_times_m - int(self.progressBar.value())):
new_val = int(self.progressBar.value()) + 1
self.progressBar.setValue(new_val)
sleep(.01)
def onFinished(self):
# progress complete
self.progressBar.setRange(0,100)
for i in xrange(int(self.progressBar.value()),101):
self.progressBar.setValue(i)
self.button.setEnabled(True)
self.button.setText('Exit')
self.button.clicked.disconnect(self.onStart)
self.button.clicked.connect(self.close)
def run(self):
self._allow_close = False
self.button.setDisabled(True)
total = self.upload_count * 2
progress_meter = 0
downloaded = []
tests_to_upload = 10
for each in xrange(tests_to_upload):
sleep(0.15)
progress_meter += 1
self.makeProgress(progress_meter,total)
sleep(0.2)
progress_meter += 1
self.makeProgress(progress_meter, total)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = ProgressBarWidget(app=app)
window.resize(640, 480)
window.show()
sys.exit(app.exec_())

MAXscript Listener can not run Pyside

Please help me !
I'm creating GUI by Python can run on the 3Ds Max, i heard someone said i have to use Pyside to make it. And everthing be fine until now.
This is my code :
import sys
from PySide import QtGui
from PySide.QtGui import *
from PySide.QtCore import *
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.setMinimumHeight(660)
self.setMinimumWidth(700)
self.setMaximumHeight(660)
self.setMaximumWidth(700)
grid = QtGui.QGridLayout()
grid.addWidget(self.First(), 0,0,2,0)
self.setLayout(grid)
self.setWindowTitle("Library")
self.resize(700, 660)
def First(self):
groupBox = QtGui.QFrame()
groupBox.setMaximumWidth(230)
groupBox.setMaximumHeight(700)
lbRenderer = QtGui.QLabel("Renderer :",self)
lbFolders = QtGui.QLabel("Folders :",self)
cbRenderer = QtGui.QComboBox(self)
cbRenderer.addItem("Vray")
cbRenderer.addItem("Octane")
lvFolders = QtGui.QListView(self)
lvFolders.setMaximumWidth(220)
lvFolders.setMaximumHeight(500)
btnAddNewObject = QtGui.QPushButton('Add New Objects',self)
btnNewSet = QtGui.QPushButton('New Set',self)
vbox = QtGui.QGridLayout()
vbox.addWidget(lbRenderer,0,0)
vbox.addWidget(cbRenderer,0,1,1,3)
vbox.addWidget(lbFolders,2,0,1,4)
vbox.addWidget(lvFolders,3,0,1,4)
vbox.setColumnStretch(1, 1)
vbox.addWidget(btnAddNewObject,4,0,1,2)
vbox.addWidget(btnNewSet,4,3)
groupBox.setLayout(vbox)
return groupBox
app = QApplication.instance()
if app is None:
app = QApplication(sys.argv)
clock = Window()
clock.show()
app.exec_()
I try another code same like my code , it run fine by "MAXScript Listener". But I dont know why when i try to run this, it dont appear anything(my GUI, or Alert is my code is not good).
First of all - you are initializing your script wrong, you call the 'initialize' function which returns #Success (meaning python initialized properly),
however you then just send in a string (which is the path to the file) and this does nothing.
What you have to use is:
python.ExecuteFile "C:\\Program Files\\Autodesk\\3ds Max 2015\\scripts\\Python\\yourPythonScript.py"
in maxscript listener\editor.
Autodesk documentation says:
Autodesk 3ds Max ships with a pre-built version of PySide 1.2
compatible with Python 2.7.3. This version includes the following
sub-set of modules:
QtCore
QtGui
QtNetwork
QtOpenGL
QtSql
QtSvg
QtTest
QtWebKit
QtXml
They have provided a simple sample script that you can run, save this in a python file, then execute it properly with the command mentioned in the beginning.
The code is here:
from PySide import QtGui
import MaxPlus
class _GCProtector(object):
widgets = []
def make_cylinder():
obj = MaxPlus.Factory.CreateGeomObject(MaxPlus.ClassIds.Cylinder)
obj.ParameterBlock.Radius.Value = 10.0
obj.ParameterBlock.Height.Value = 30.0
node = MaxPlus.Factory.CreateNode(obj)
time = MaxPlus.Core.GetCurrentTime()
MaxPlus.ViewportManager.RedrawViews(time)
return
app = QtGui.QApplication.instance()
if not app:
app = QtGui.QApplication([])
def main():
MaxPlus.FileManager.Reset(True)
w = QtGui.QWidget()
w.resize(250, 100)
w.setWindowTitle('Window')
_GCProtector.widgets.append(w)
w.show()
main_layout = QtGui.QVBoxLayout()
label = QtGui.QLabel("Click button to create a cylinder in the scene")
main_layout.addWidget(label)
cylinder_btn = QtGui.QPushButton("Cylinder")
main_layout.addWidget(cylinder_btn)
w.setLayout(main_layout)
cylinder_btn.clicked.connect(make_cylinder)
if __name__ == '__main__':
main()
They also mention this which is important:
Normally one creates a PySide application object in a script using
QtGui.QApplication(). However, in 3ds Max, there is already a PySide
application running, so you get a handle for that object like this:
QtGui.QApplication.instance()
Use that as a start script, and port your GUI items into that and it should get you up and running.
I tried to fix your code but anything happen, i dont know why.
First thing , i opened your code and run it in Pycharm but it can not run. But it totally run in Maxscript Listener, could you explain to me ?
Second i tried to fix your code. It's all the same, i can run it on Maxscript, but the content and function inside is disappear.
This is my code
from PySide import QtGui
import MaxPlus
class _GCProtector(object):
widgets = []
app = QtGui.QApplication.instance()
if not app:
app = QtGui.QApplication([])
def main():
MaxPlus.FileManager.Reset(True)
w = QtGui.QWidget()
w.setWindowTitle('Window')
_GCProtector.widgets.append(w)
w.show()
main_layout = QtGui.QGridLayout()
main_layout.addWidget(First(),0,0)
main_layout.addWidget(Second(),0,1)
w.setLayout(main_layout)
def First():
groupBox = QtGui.QFrame()
lbRenderer = QtGui.QLabel("Renderer :",self)
vbox = QtGui.QGridLayout()
vbox.addWidget(lbRenderer,0,0)
groupBox.setLayout(vbox)
return groupBox
def Second():
groupBox = QtGui.QFrame()
lbRenderer = QtGui.QLabel("Renderer :",self)
vbox = QtGui.QGridLayout()
vbox.addWidget(lbRenderer,0,0)
groupBox.setLayout(vbox)
return groupBox
if __name__ == '__main__':
main()
And this is the alert from Maxcript

Events with QGraphicsItemGroup

In my application I want to use QGraphicsItemGroup for grouping items into one item.
I played with it a little and not sure using it because when I want to catch events, events are merged together but I want to handle specific event with specific child.
How can I achieve this?
You need to call QGraphicsItemGroup::setHandlesChildEvents(false). This stops the QGraphicsItemGroup trying to handle the event, and lets the child QGraphicsItems handle them instead.
I think that's the point of the QGraphicsItemGroup. Judging from the documentation, this is meant to simplify moving and transforming multiple items at once e.g. imagine the following case: a user draws a selection rectangle around several items in an application because he wants to move all of them. Perhaps what you want more is to create a hierarchy of items, e.g. have one parent item with several child items. This way you'll get the individual events for each item. This can be accomplished by calling QGraphicsItem::setParentItem();
The question doesn't specify which version of Qt is concerned and there is a correct answer for Qt4. Here is an answer for Qt5 (it works for PyQt5 and I guess it'll work also in C++). The solution was to reimplement sceneEvent, reimplementing a specialized event-catcher such as contextMenuEvent was not sufficient.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
class GraphicsItem(QtWidgets.QGraphicsItem):
def __init__(self,
rect: QtCore.QRectF,
name: str,
parent: QtWidgets.QGraphicsItem = None):
super().__init__(parent)
self._name = name
self._rect = rect
def boundingRect(self):
return self._rect
def paint(self,
painter: QtGui.QPainter,
option: QtWidgets.QStyleOptionGraphicsItem,
widget: QtWidgets.QWidget = None):
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.red))
painter.drawRect(self._rect)
def sceneEvent(self, event: QtCore.QEvent):
if (event.type() == QtCore.QEvent.GraphicsSceneContextMenu):
self.contextMenuEvent(event)
event.accept()
return True
def contextMenuEvent(self, event: QtWidgets.QGraphicsSceneContextMenuEvent):
print(f'contextMenuEvent in "{self._name}"')
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._scene = QtWidgets.QGraphicsScene()
layout = QtWidgets.QHBoxLayout()
self._view = QtWidgets.QGraphicsView(self._scene)
layout.addWidget(self._view)
self._widget = QtWidgets.QWidget()
self._widget.setLayout(layout)
group = QtWidgets.QGraphicsItemGroup()
self._scene.addItem(group)
scene_item = GraphicsItem(QtCore.QRectF(0, 0, 100, 100), 'in scene')
self._scene.addItem(scene_item)
group_item = GraphicsItem(QtCore.QRectF(150, 0, 100, 100), 'in group')
group.addToGroup(group_item)
group_item = GraphicsItem(QtCore.QRectF(300, 0, 100, 100), '2nd in group')
group.addToGroup(group_item)
self.setCentralWidget(self._widget)
self.setWindowTitle('contextMenuEvent with QGraphicsItemGroup')
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.setGeometry(100, 100, 800, 500)
mainWindow.show()
sys.exit(app.exec_())