Goal:
To start multiple animations all at once.
Expected Result:
All animation defined and started at once must run parallel.
Actual Result:
The only animation that has been started first shows, the below animations doesn't even start.
The Code for Reference:
class KivySplash(Screen):
def __init__(self, **kwargs):
super(KivySplash, self).__init__(**kwargs)
anim1 = MyAnimation(duration=4, opacity=0)
anim1.bind(on_complete=self.on_anim1_complete)
self.animation = MyAnimation(duration=3) + MyAnimation(duration=4, opacity=1) + MyAnimation(duration=5) + anim1
self.img1 = Image(source=os.path.join(original_dir, "Kivy-logo-black-512.png"), opacity=0)
self.img2 = Image(source=os.path.join(original_dir, "python-powered-w-200x80.png"))
self.label1 = Label(text="Powered by:", font_size=48)
box_layout = BoxLayout(orientation="vertical")
box_layout1 = BoxLayout()
box_layout.add_widget(self.label1)
box_layout1.add_widget(self.img1)
box_layout1.add_widget(self.img2)
box_layout.add_widget(box_layout1)
self.add_widget(box_layout)
def on_anim1_complete(self, *args):
do_nothing(self, *args)
if self.img1 in self.animation.animated_widgets:
pass
def on_enter(self, *args):
self.animation.start(self.img1)
self.animation.start(self.img2)
Thanking You.
I believe you have encountered a bug in the kivy Animation. If you are just using a simple Animation, then starting that Animation on multiple Widgets should work fine. The bug happens when you are using a Sequence (Animations connected with '+'). Sequences work by running the first Animation and binding an internal on_complete method that starts the next Animation in the Sequence. When you call start, that on_complete method is bound. But as soon as the first Animation on the first Widget in the Sequence completes, the second Animation is started and the on_complete method is unbound. Now, when the first Animation on the second Widget completes, the on_complete is not called (is was unbound after the first Widget completed), and the second Animation is not started.
Here is the code from Sequence:
def on_anim1_complete(self, instance, widget):
self.anim1.unbind(on_complete=self.on_anim1_complete)
self.anim2.start(widget)
In your case, it looks like the Animation is not starting on the second Widget, but because your first Animation doesn't actually animate anything, you don't see it.
Unfortunately, there are not many alternatives to avoid this problem.
You can build a copy of the entire Animation a second time (copy() or deepcopy() will not work), and just use two different animations (one for each Widget).
You can do your own sequencing by just using simple Animations and use your own on_complete to start the next Animation. Conveniently, the on_complete arguments includes the animated widget that you need for the next start() call.
In some situations, you may be able to animate a single container (like a Layout). Since that is only animating a single Widget, the sequencing should work correctly.
Related
I set up a Jupyter Notebook where I'm running a recursive function that clears the output like so, creating an animated output:
from IPython.display import clear_output
active = True
def animate:
myOutput = ""
# do stuff
clear_output(wait=True)
print(myOutput)
sleep(0.2)
if active:
animate()
and that's working perfectly.
But now I want to add in one more step: A speed toggle. What I'm animating is a debugging visualization of a cursor moving through interpreted code as an interpreter I'm writing parses that code. I tried conditional slow-downs to have more time to read what's going on as the parsing continues, but what I really need is to be able to click a button to toggle the speed between fast and slow. Maybe I'll use a slider, but for now I just want a button for proof of concept.
This sounds simple enough. Note that I'm writing this statefully as a class because I need to read / write the state from within another imported class.
Jupyter block 1:
import ipywidgets as widgets
from IPython.display import display
out = widgets.Output()
class ToggleState():
def __init__(self):
self.button = widgets.Button(description="Toggle")
self.button.on_click(self.toggle)
display(self.button)
self.toggleState = False
print("Toggle State:", self.toggleState)
def toggle(self, arg): # arg has to be accepted here to meet on_click requirements
self.toggleState = not self.toggleState
print("Toggle State:", self.toggleState)
def read(self):
return self.toggleState
toggleState = ToggleState()
Then, in Jupyter block 2, note I decided to to do this in a separate block because the clear_output I'm doing with the animate func clears the button if it's in the same block, and therein lies the problem:
active = True
def animate:
myOutput = ""
# do stuff
clear_output(wait=True)
print(myOutput)
if toggleState.read():
sleep(5)
else:
sleep(0.2)
if active:
animate()
But the problem with this approach was that two blocks don't actually run at the same time (without using parallel kernels which is way more complexity than I care for) so that button can't keep receiving input in the previous block. Seems obvious now, but I didn't think about it.
How can I clear input in a way that doesn't delete my button too (so I can put the button in the animating block)?
Edit:
I thought I figured the solution, but only part of it:
Using the Output widget:
out = widgets.Output()
and
with out:
clear_output(wait=True) # clears only the logged output
# logging code
We can render to two separate stdouts within the same block. This works to an extent, as in the animation renders and the button isn't cleared. But still while the animation loop is running the button seems to be incapable of processing input. So it does seem like a synchronous code / event loop blocking problem. What's the issue here?
Do I need an alternative to sleep that frees up the event loop?
Edit 2:
After searching async code in Python, I learned about asyncio but I'm still struggling. Jupyter already runs the code via asyncio.run(), but the components obviously have to be defined as async for that to matter. I defined animate as async and tried using async sleeps, but the event loops still seems to be locked for the button.
this is a bit longer, the first part is just a description of the problem, the second one the question if my "fix" is correct.
I started with python programming. I created a program that communicates with an Arduino that reads the temperature of a furnace of our melting lab. The temperature is then used in a PID algorithm and an output is set to the Arduino. The communication is done via pyserial. So far, everthing works, including live plotting of the temperature signals, PID-variables and so on. The script includes a the main loop and 3 threads (serial communication, a datashifter that reads from serialport, the set temperature from the QWidget and the output of the PID algorithm. This values are used to create an array for displaying within pyqtgraph. Finally, the third thread shifts the data from the datashifter to the QWidget.
When using my Linux-Notebook, everything works fine, and the GUI never stops updating. In contrast, when using any Windows-Host, i have the problem that some pyqtgraphs stop to refresh. The behavior is strange, because i set all data at more or less the same time, with the same numpy array (just different columns) - some plots refresh longer (hours), some stop earlier (minutes). After searching more or less the hole internet ;-) I think that I found the problem: Its the passing of data from from a thread to the GUI. Some dummy code to explain what's going on:
DataUpdaterToGUI(QThread):
#sets the QWidget from main loop
def setGUI(self, gui):
self.gui = gui
def run()
while True:
with lock(): # RLock() Instance
copyArray = self.dataArray[:] # copy the array from the shifter
self.gui.plot1.copyArray(dataArray[:, 0], copyArray[:, 1])
self.gui.plot2.copyArray(dataArray[:, 0], copyArray[:, 2])
# self.gui.update()
# QApplication.instance().processEvents()
Neither calling self.gui.update() nor processEvents() has any influence on the outcome: The plots stop redrawing after a while (on windows).
Now i have a very simple example, and just want to make sure if I'm using the threading-stuff correctly. It works fine, but I have some questions:
Does the signal-slot approach copy the passed data?
Why is it not necessary to call the update() method of the QWidget?
Do I have to use any kind of locks when using signals?
class Main(QWidget):
def __init__(self):
super().__init__()
self.layout = QGridLayout(self)
self.graph = pg.PlotWidget()
self.graph.setYRange(0,1000)
self.plot = self.graph.plot()
self.layout.addWidget(self.graph,0,0)
self.show()
def make_connection(self, data_object):
data_object.signal.connect(self.grab_data)
#pyqtSlot(object)
def grab_data(self, data):
print(data)
self.plot.setData(data)
class Worker(QThread):
signal = pyqtSignal(object)
def __init__(self):
super().__init__()
def run(self):
self.data = [0, 1]
i = 2
while True:
self.data[1] = i
self.signal.emit(self.data)
time.sleep(0.01)
i += 1
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Main()
worker = Worker()
widget.make_connection(worker)
worker.start()
sys.exit(app.exec_())
Does the signal-slot approach copy the passed data? The signals are thread-safe and when transferring data they make a copy so the thread that precedes the data and the thread that consumes it (GUI Thread) will not have conflicts
Why is it not necessary to call the update() method of the QWidget? Actually pyqtgraph calls the update method, plot is a PlotDataItem, so if we check the source code of setData() method, it calls the updateItems() method, in that method the setData() method of the curve or scatter attribute is called (according to the type of graphics), in the case of curve its setData() method calls updateData(), and the updateData() method calls update, and in the case of the scatter its setData() method calls addpoint(), and addPoints() calls invalidate(), and this invalidate() method calls update().
Do I have to use any kind of locks when using signals? No, as the signals are thread-safe so Qt already has the guards set to avoid the collision.
I have a PyQt5 application that looks like this This. Now, with the "bailar" button I want the character on the grid to look left and right a couple of times. I do this in the following method:
def dance(self):
"""
Make the P1 dance
"""
p1 = self._objects['P1']
x = p1.x
y = p1.y
cell = self.worldGrid[y][x]
for i in xrange(3):
print("Moving my head...")
cell.objectLeaves('P1')
p1.pixmap = p1.pixmap.transformed(QTransform().scale(-1, 1))
cell.objectArrives('P1', p1)
time.sleep(0.2)
However, the label containing the pixmap updates only at the last iteration. I know this must be a problem of the update function being asynchronous and the time.sleep() blocking the main thread, but I don't know how else could I show the animation. I tried using a QThread with the moveToThread method, but it failed as the gird widget is a child of the main window. Any ideas?
You see only the last frame of your animation because the UI isn't updated by Qt until the dance function terminates. If you add QtGui.qApp.processEvents() just before the time.sleep(0.2) statement, the UI will be updated for every frame of the animation.
Note that the user still cannot interact with your application until the animation has finished. This might not be a problem for short animations like this, but for longer animations you might better use a QTimer or the Qt animation framework that Brendan Abel suggested.
I am using matplotlib timer objects to register my own update function for an animation. I can't seem to stop the callbacks once they start though without keeping a reference to the timer object.
It has been my experience thus far that when I create an object in matplotlib I am given a reference to it but it is also added to a list inside some other object (axis in figures, lines in axis etc.) which can then be queried later. I cannot find where timer objects live however. My problem can be summarized by this code snippet
import matplotlib.pyplot as plt
import numpy as np
def update():
plt.get_current_fig_manager().canvas.figure.patch.set_facecolor(str(np.random.random()))
plt.draw()
def start_animation():
timer = fig.canvas.new_timer(interval = 50)
timer.add_callback(update)
timer.start()
fig = plt.figure()
start_animation()
Run the above code snippet, then try to programmatically stop the flashing. The function that needs to get called is
timer.remove_callback(update).
To be clear. I know that I can just keep a reference to a timer object and this problem goes away. I am looking for an explanation of where this object must be living in matplotlib.
how bout
self.timer = fig.canvas.new_timer(interval=100)
...
self.timer.remove_callback(...)
to clarify the reference is in a callafter method.
there is no reference stored in your figure or your canvas
you can see this in the backend source
def new_timer(self, *args, **kwargs):
"""
Creates a new backend-specific subclass of :class:`backend_bases.Timer`.
This is useful for getting periodic events through the backend's native
event loop. Implemented only for backends with GUIs.
optional arguments:
*interval*
Timer interval in milliseconds
*callbacks*
Sequence of (func, args, kwargs) where func(*args, **kwargs) will
be executed by the timer every *interval*.
"""
return TimerTk(self._tkcanvas, *args, **kwargs)
which simply returns a TimerTK instance. the reference continues to live because the in the TimerTk.start() method you see a callafter that continues to keep the timer from garbage collecting
class TimerTK(TimerBase):
...
def _timer_start(self):
self._timer_stop()
self._timer = self.parent.after(self._interval, self._on_timer)
and that is why every example shows saving your own reference to the timer
I am trying to bind events from a GUI file to use code from another file (effectively a "front end" and a "back end"). I can get the back end and front end working within the same file, but when I try to move them into separate files, I have issues getting the back end to see parts (labels, buttons, etc.) of the front end.
I. E. I need the back end code to change labels and do math and such, and it would need to affect the GUI.
I have provided a simple version of my program. Everything works with the exception of the error I get when I try to make the back end see the parts of the GUI.
mainfile.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
import label_changer
class foopanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
box = wx.BoxSizer()
btn = wx.Button(self,1,"Press")
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
box.Add(btn)
self.lbl = wx.StaticText(self,1,"Foobar")
box.Add(self.lbl)
self.SetSizerAndFit(box)
class main_frame(wx.Frame):
"""Main Frame holding the main panel."""
def __init__(self,*args,**kwargs):
wx.Frame.__init__(self,*args,**kwargs)
sizer = wx.BoxSizer()
self.p = foopanel(self)
sizer.Add(self.p,1)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = main_frame(None,-1,)
app.MainLoop()
label_changer.py
def change_label(self):
self.p.lbl.SetLabel("barfoo")
All I want it to do is change the label of the GUI, but use an external file.
I am doing this mostly to keep my code separate and just as a learning experience.
Thanks in advance!
One solution is to modify change_label to accept an argument that identifies the label to change. For example:
def change_label(event, label):
label.SetLabel("barfoo")
Then, use lambda to create a callback that passes that argument in:
btn.Bind(wx.EVT_BUTTON, label_changer,
lambda event, label=self.p.lbl: label_changer.change_label(event, label))
Make sure you define self.lbl before you do the binding.
For more on passing arguments to callbacks see Passing Arguments to Callbacks on WxPyWiki
A common way to do this is the MVC Pattern and pubsub. See this Example.
This
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
needs to be
btn.Bind(wx.EVT_BUTTON,label_changer.change_label)
and this
def change_label(self):
self.p.lbl.SetLabel("barfoo")
needs to be
def change_label(event):
panel = event.GetEventObject().GetParent()
panel.lbl.SetLabel("barfoo")
To clarify, you need to pass a reference to a function to Bind that is to be called when the event occurs. wx will always pass one argument to these functions - the event. The self that you usually see in the callbacks is a byproduct of them being bound methods. Every bound method (to oversimplify, a function defined in a class) gets implicitly passed a first argument when called that is a reference to a class instance. So since you can't get to this instance the traditional way in an "external" function you have to get to it through the event object.
One more thing, you are not realy separating the gui from the logic this way. This is because the logic (label_changer in this case) needs to know about the gui and to manipulate it directly. There are ways to achieve much stronger separation (st2053 hinted at one of them) but for a relatively small program you don't need to bother if you don't want to right now, simply splitting the code in multiple files and focusing on getting the thing done is fine. You can worry about architecture later.