I am making a Kivy application right now, and in one part of it, I am getting data as an array of floating point numbers and I want to draw a line in Kivy using the data.
The problem is, I want it to constantly run, so I used threading, but Kivy would not draw the line. here is a stripped down version of the code that illustrates the problem:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line
from threading import Thread
class MyWidget(Widget):
def Draw(self):
with self.canvas:
Line(points=[100, 200, 300, 400])
class MainApp(App):
def build(self):
return MyWidget()
Thread(target=MyWidget().Draw).start()
MainApp().run()
I want this code to draw a line with points 100, 200, 300, 400.
but instead, the app opens and does nothing, help will be appreciated!
I modified your example a bit.
Try start the thread in the init method instead. Because when you do MyWidget().Draw, you do that with a new MyWidget object, and not the one you returned in your build method. So that line will never be drawn. But the line in another widget which is not on the screen.
Try like this:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line, InstructionGroup
from threading import Thread
from random import randint
import time
class MyWidget(Widget):
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
self.ig = InstructionGroup()
self.line = Line(points=[100, 200, 300, 400])
self.ig.add(self.line)
self.canvas.add(self.ig)
Thread(target=self.draw).start()
def draw(self):
while True:
self.line.points = [randint(0,400) for i in range(4)]
time.sleep(0.5)
class MainApp(App):
def build(self):
return MyWidget()
MainApp().run()
Related
I'm working on an app, and I need the images to display independently at a specific timing. I have set up a thread using python's stock threading module, it runs and works normally instead of the image it displays a black square. Does anyone know how to fix it?
Here is my code to reproduce the issue:
import threading
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
class TestApp(App):
def build(self):
self.fl = FloatLayout()
self.fl.add_widget(Button(text="show image", on_press=self.start_thread))
return self.fl
def insertfunc(self):
self.fl.add_widget(Image(source="HeartIcon.png"))
def start_thread(self, instance):
threading.Thread(target=self.insertfunc).start()
TestApp().run()
Any help will be appreciated!
The add_widget() must be done on the main thread. I assume that you are using threading because you have additional things to do on the Thread aside from just the add_widget(). Based on that assumption, here is a modified version of your code that does what I think you want:
import threading
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.image import Image
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
class TestApp(App):
def build(self):
self.fl = FloatLayout()
self.fl.add_widget(Button(text="show image", on_press=self.start_thread))
return self.fl
def insert_image(self, dt):
self.fl.add_widget(Image(source="HeartIcon.png"))
def insertfunc(self):
# do some calculations here
Clock.schedule_once(self.insert_image)
def start_thread(self, instance):
threading.Thread(target=self.insertfunc).start()
TestApp().run()
If you are not doing anything else in the new thread, then you don't actually need another thread. The start_thread() method can just do the:
self.fl.add_widget(Image(source="HeartIcon.png"))
I have problem with disabling of buttons in kivy library. When I disable button, it simply not disable. It waits in some strange way.
Let me show you my code:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
import time
class MainApp(App):
def build(self):
self.l = FloatLayout()
b = Button(text="BUTTON", pos_hint={"top":0.8, "right": 0.8}, size_hint=(0.6, 0.6))
b.bind(on_press=self.press)
self.l.add_widget(b)
return self.l
def press(self, btn):
btn.disabled = True
time.sleep(3.0)
btn.disabled = False
app = MainApp()
app.run()
When I press button, I want to disable it for 3 sec. But instead of it program "freeze" (without disabling of button), and then after 3 secs do animation of press (button blinks with blue color). Of cource program must "freeze" because of time.sleep(3.0), but after disabling of button (Which must be gray, but it dont change color...)
How to solve it? If I put there instead time.sleep() something like for cycle (with about 10 milions of cycle) to imitate of "doing something" by program, it behaves in the same way...
So how I can solve it? How to disable button in kivy, then do something and after it is done enable button again?
Thanks!
EDIT: My problem isn't, that program freezes for 3 seconds. I understand that calling time.sleep() is blocking. What I don't understand is why button is not disabled before (and during) sleep...
The time.sleep is blocking the code. Instead you need to use Clock to enable the button after 3 seconds. Below is the corrected code to achieve your target:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock
from functools import partial
class MainApp(App):
def build(self):
self.l = FloatLayout()
b = Button(text="BUTTON", pos_hint={"top":0.8, "right": 0.8}, size_hint=(0.6, 0.6))
b.bind(on_press=self.press)
self.l.add_widget(b)
return self.l
def press(self, btn):
btn.disabled = True
Clock.schedule_once(partial(self.btn_enable, btn), 3)
def btn_enable(self, btn, *args):
btn.disabled = False
app = MainApp()
app.run()
TL; DR
The animation happens after the press function is called. This means that you freeze the program when doing time.sleep.
What to do about it?
Instead, you need to do something non-blocking, meaning that it runs in three seconds, but it doesn't cause the program to freeze. Something that would probably work is to utilize threads (something similar to the example, but dealing with sending variables across threads).
Example
Here is an example for your code that does not work, so you can understand the gist of it. Most likely, you are going have to deal with passing variables across threads:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
# import time
import threading
class MainApp(App):
def build(self):
self.l = FloatLayout()
b = Button(text="BUTTON", pos_hint={"top":0.8, "right": 0.8}, size_hint=(0.6, 0.6))
b.bind(on_press=self.press)
self.l.add_widget(b)
return self.l
def press(self, btn):
btn.disabled = True
# time.sleep(3.0)
threading.Timer(3.0, lambda: btn.disabled = False).start()
app = MainApp()
app.run()
This was inspired by this answer.
I have made an easy app where i try to show my issue. When using python to draw a line in kivy (using the with self.canvas method) the line gets drawn from a center_x and center_y of 50.
Using kivy Lang draws the line correctly in the center. Here is a simple code example:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line
class TestWidget(Widget):
def draw(self):
with self.canvas:
Line(points=[self.center_x,self.center_y,self.center_x,self.center_y+100], width=2)
class TestApp(App):
def build(self):
test = TestWidget()
test.draw()
return test
if __name__ == '__main__':
TestApp().run()
and the corresponding test.kv file:
#:kivy 1.11.1
<TestWidget>:
canvas:
Line:
width: 5
points: (self.center_x,self.center_y,self.center_x,self.center_y+100)
result is like this:
Any idea why using python code is not working?
Both codes are not equivalent, in the case of python you are only establishing that the position of the line is based on the center of the widget at that time, that is, at the beginning, instead in .kv it is indicating that the line position is always it will be with respect to the center of the Widget.
TL; DR;
Explanation:
In kv the binding is a natural and implicit process that in python is not doing it, besides that its implementation is simpler, for example the following code:
Foo:
property_i: other_object.property_j
In that case its equivalent in python is:
foo = ...
other_object = ...
other_object.bind(property_j=foo.setter("property_i"))
And it is not:
foo.property_i = other_object.property_j
Since with the bind you are indicating before a change of property_j update property_i with that value, but in the last code you only indicate at this moment copy the value of property_j in property_i.
In your particular case, the center you take is before displaying the widget and kivy taking default configurations and other properties into consideration, changes the geometry of the widget after it is displayed.
Solution
Making the necessary modifications taking into account the previous explanation the following code is an equivalent of the .kv code
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Line
class TestWidget(Widget):
def __init__(self, **kwargs):
super(TestWidget, self).__init__(**kwargs)
with self.canvas:
self.line = Line(width=2)
self.bind(center=lambda *args: self.draw())
def draw(self):
self.line.points = [
self.center_x,
self.center_y,
self.center_x,
self.center_y + 100,
]
class TestApp(App):
def build(self):
test = TestWidget()
return test
if __name__ == "__main__":
TestApp().run()
With kv language your declarations automatically update when their dependencies change. That means that when the widget's centre goes from (50, 50) to something else, the line is redrawn in the new location.
Your Python code doesn't do this, it just draws the line once using whatever its centre is at the moment the code runs. That value is (50, 50), since that's the default before positioning has taken place.
The solution is to write code in Python that updates the line when the widget centre changes, something along the lines of declaring the line with self.line = Line(...) and self.bind(centre=some_function_that_updates_self_dot_line).
this is my script ...
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.image import Image
class MyApp(App):
def build(self):
return Image(source='go.jpg')
MyApp().run()
I won't to reload it becose the image is changing and I won't to see the new won all time
you can use reload() the read the image from the disk again
this will reload even if the original data of the image is updated or changed
self.ids.image1.source = './Images/file.png'
self.ids.image1.reload()
Make your own widget class, and make the image an attribute, so you can reference it. Then use clock to schedule an interval method, to constantly reload the image.
In the example below, the update_pic method is executed once every second.
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.uix.widget import Widget
class MyImageWidget(Widget):
def __init__(self,**kwargs):
super(MyImageWidget,self).__init__(**kwargs)
self.image = Image(source='go.jpg')
self.add_widget(self.image)
Clock.schedule_interval(self.update_pic,1)
def update_pic(self,dt):
self.image.reload()
class MyApp(App):
def build(self):
return MyImageWidget()
MyApp().run()
You can use the Image.reload method
def build(self):
img = Image(source='go.jpg')
Clock.schedule_interval(lambda dt: img.reload(), 0.2) #5 per second
return img
I am trying to plot various line segments using kivy. The line appears really small (at one end of the screen) and I would like to scale it up. Ideally, I would like to specify coordinates from the center of screen and specify width and height so that the line segments appear well. Here is the code I wrote:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.graphics import Color, Ellipse, Line
class MyWidget(Widget):
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
with self.canvas:
for obstacle in obstacles:
print obstacle
Line(points=[20, 5, 40, 5],width=1)
pass
# add your instruction for main canvas here
class MotionPlanningApp(App):
def build(self):
root = GridLayout(cols=1, padding=5, spacing=1)
root.add_widget(MyWidget())
return root
if __name__ == '__main__':
MotionPlanningApp().run()
Is there some way to do this in kivy?
You can use Translate instructions to move the line (effectively moving the origin of the canvas) and Scale instructions to stretch it.
For instance, you could have:
from kivy.core.window import Window
with self.canvas:
self.translate = Translate(Window.width / 2, Window.height / 2.)
You would also need to modify the translate instruction later if the window size changed, by binding a function to that.
Regardless, though, the coordinates are in screen pixels so the line is currently small just because you only made it 20 pixels long.