I am trying to draw a custom shape with kivy using "mesh" in python.
I did some research on this but most of the result is just write the code in the python file
The code from here and here shows the way to construct mesh object in the python file but i found a problem when i try to translate it into kivy file
this is the code in my main file(main.py):
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.graphics import Mesh
from kivy.properties import ObjectProperty
class MainScreen(Screen):
Mesh = ObjectProperty(None)
class TestApp(App):
def build(self):
return Builder.load_file("health.kv")
sample_app = TestApp()
sample_app.run()
and this is the code in my kivy file(test.kv):
<MainScreen>:
name: "main"
Mesh:
vertices: [0, 0, 0, 0, 100, 0, 0, 0, 100, 100, 0, 0]
indices: [0, 1, 2]
The error goes as below:
File "C:\Users\kelv1\AppData\Local\Programs\Python\Python37-32\lib\site-packages\kivy\uix\floatlayout.py", line 135, in add_widget
widget.bind(
AttributeError: 'kivy.graphics.vertex_instructions.Mesh' object has no attribute 'bind'
Why does it happens and how to solve it??
You just forgot to put it into a canvas. And I added the triangle_fan mode, to make it into polygon. Just guessing that is what you want.
Try this:
from kivy.app import App
from kivy.lang import Builder
KV = """
<MainScreen#Screen>:
name: "main"
canvas:
Mesh:
mode: "triangle_fan"
vertices: [0, 0, 0, 0, 100, 0, 0, 0, 100, 100, 0, 0]
indices: [0, 1, 2]
MainScreen:
"""
class MyApp(App):
def build(self):
return Builder.load_string(KV)
MyApp().run()
Related
Using kivy, I am trying to create my own dropdown menu to solve some issues I have with the built in DropDown widget. However, when resizing my widget to animate an opening sequence, the widget size remains the same. Upon further investigation I realised that the widget is always the same size as the parent RelativeLayout but I can't find a reason why. Here is a minimum working code
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.uix.relativelayout import RelativeLayout
from kivy.lang import Builder
import kivy.properties as props
KV = '''
#:import MaterialWidget materialwidget.MaterialWidget
<DropDown#RelativeLayout>
_background: background
canvas:
Color:
rgba: 1, 0, 0, 1
Rectangle:
pos: root._pos
size: root._size
Button:
id: background
size: root._size
pos: root._pos
on_size: print("size_changed", self.size)
'''
class DropDown(RelativeLayout):
opened = props.BooleanProperty(False)
anchor = props.OptionProperty("tl", options=["tr", "tl", "br", "bl"])
_size = props.ListProperty([0, 0])
_pos = props.ListProperty([0, 0])
_background = props.ObjectProperty(None)
def __init__(self, **kwargs):
super(DropDown, self).__init__(**kwargs)
#self.add_widget(self._background)
trigger = Clock.create_trigger(lambda *args: self.on_pos())
trig = Clock.create_trigger(lambda *args: self.on_size())
self.bind(
anchor=trigger,
_size=trigger,
opened=trig
)
def on_pos(self, *args):
# calculate new pos
invert_x = "r" in self.anchor
invert_y = "t" in self.anchor
self._pos = (
self.pos[0] - self._size[0] if invert_x else 0,
self.pos[1] - self._size[1] if invert_y else 0
)
def on_size(self, *args):
anim = Animation(
_size=self.size if self.opened else (0, 0),
duration=0.5,
transition="in_out_circ"
)
anim.start(self)
def open(self, *args):
# toggle the value of open state
self.opened = not self.opened
Builder.load_string(KV)
if __name__ == "__main__":
from kivy.app import runTouchApp
from kivy.uix.button import Button
from kivy.core.window import Window
class Test(Button):
def __init__(self, **kwargs):
super(Test, self).__init__(**kwargs)
self.size = (100, 100)
self.size_hint = (None, None)
self.dropdown = DropDown(pos=(200, 100), size=(400, 300))
self.add_widget(self.dropdown)
self.bind(on_release=lambda *args: self.dropdown.open(self))
Window.clearcolor = (1, 1, 1, 1)
runTouchApp(Test())
You probably need to set size_hint: None, None on your DropDown. If size_hint is not None, it will over-ride any size setting (including animations of the size property).
How to implement threading when building buttons with Kivy in my class
And a separate thread on buttonpress when calling start in :text="START",size_hint=(SizeW, SizeH), pos_hint={'x': .0, 'y': ButtonY}, on_press=start)
Or if you have any suggestions how this can be better done or how it should be done i would really appriciate it im really stuck on this
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.core.window import Window
from filex import start
class Hello(FloatLayout):
def __init__(self, **kwargs):
super(Hello, self).__init__(**kwargs)
Window.size = (800, 150)
Window.clearcolor = (0, 0, 1, 1)
# Main Buttons
ButtonY = 0.5
SizeH = 0.5
SizeW = 0.33
self.walk_button = Button(background_normal='image4.png',
border=(0, 0, 0, 0),
text="START",size_hint=(SizeW, SizeH), pos_hint={'x': .0, 'y': ButtonY}, on_press=start)
self.help_button = Button(background_normal='image1.png',
border=(0, 0, 0, 0),
text="Help", size_hint=(SizeW, SizeH), pos_hint={'x': .67, 'y': 0},on_press=self.update)
self.go_button = Button(background_normal='image2.png',
border=(0, 0, 0, 0),
text="Go", size_hint=(SizeW, SizeH), pos_hint={'x': .335, 'y': 0})
self.add_widget(self.help_button)
self.add_widget(self.go_button)
self.add_widget(self.walk_button)
self.current_text = "Default"
def update(self,event):
self.walk_button.text = "Changed to change"
class Example(App):
def build(self):
return Hello()
if __name__ == '__main__':
x = Example();
x.run();
You can use threading by calling:
threading.Thread(target=self.update).start()
This is a good idea when your button is starting a method that takes a significant time to run. (Significant is any length of time that you do not want your GUI blocked). However, anything that modifies the GUI must be done on the main thread,
so the update() method in your question is not a candidate for running in a separate thread. If you have a long running method that also needs to modify the GUI, then run it in a separate thread, but call another method that only does the GUI modifications using:
Clock.schedule_once()
I am working on a project on which I have a GUI (coded by hand) with two tabs, and on each tab I have a different canvas (to plot different things in each tabs).
But, I added also some widgets on these tabs and when I add them to the layout, if I add the canvas at the same position of a button in the layout for example, I can click on this button anymore.
I know on PyQt it is possible to raise the level of the widget, so is there a way to do the same thing with a canvas?
Thank you in advance for your help. On this example, the "Quit" is active only on the right half.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
class FenetrePrincipale(QWidget):
def __init__(self, parent=None):
super(FenetrePrincipale, self).__init__(parent)
self.setupUi(self)
# Fonction de configuration de la classe
def setupUi(self, Form):
self.Form = Form
Form.setMinimumSize(1220, 850)
self.creation_GUI()
self.creation_figure()
self.creation_layout()
self.tabWidget.setCurrentIndex(0)
self.Bouton_quitter.clicked.connect(self.close)
def resizeEvent(self, QResizeEvent):
self.tabWidget.setMinimumSize(QSize(self.width() - 20, self.height() - 60))
def creation_GUI(self):
self.tabWidget = QTabWidget()
self.tab1 = QWidget()
self.Widget_choixPalette_Label = QLabel(self.tab1)
self.Widget_choixPalette_Label.setText("Text1")
self.Widget_choixPalette_ComboBox = QComboBox(self.tab1)
self.Widget_choixPalette_ComboBox.addItem("Try1")
self.Widget_choixPalette_ComboBox.addItem("Try2")
self.Bouton_quitter = QPushButton(self.tab1)
self.Bouton_quitter.setText("Quit")
def creation_layout(self):
LayoutForm = QGridLayout(self.Form)
LayoutG1 = QGridLayout()
LayoutTab1 = QGridLayout(self.tab1)
WidgetTemp = QWidget()
LayoutWidgetTemp = QGridLayout()
LayoutG1.addWidget(self.Bouton_quitter, 21, 29, 1, 2, Qt.AlignRight | Qt.AlignBottom)
LayoutG1.addWidget(self.canvas, 2, 10, 20, 20)
LayoutWidgetTemp.addWidget(self.Widget_choixPalette_Label, 0, 0, 1, 4)
LayoutWidgetTemp.addWidget(self.Widget_choixPalette_ComboBox, 1, 0, 1, 4)
WidgetTemp.setLayout(LayoutWidgetTemp)
LayoutG1.addWidget(WidgetTemp, 1, 18, 2, 4)
LayoutTab1.addLayout(LayoutG1, 0, 0, 1, 1)
self.tabWidget.addTab(self.tab1, " Tab1 ")
LayoutForm.addWidget(self.tabWidget, 1, 0, 1, 1)
def creation_figure(self):
# Create figure (transparent background)
self.figure = plt.figure()
self.figure.patch.set_facecolor('None')
self.canvas = FigureCanvas(self.figure)
self.canvas.setStyleSheet("background-color:transparent;")
# Adding one subplot for image
self.axe0 = self.figure.add_subplot(111)
self.axe0.get_xaxis().set_visible(False)
self.axe0.get_yaxis().set_visible(False)
plt.tight_layout()
# Data for init image
self.imageInit = [[255] * 320 for i in range(240)]
self.imageInit[0][0] = 0
# Init image and add colorbar
self.image = self.axe0.imshow(self.imageInit, interpolation='none')
divider = make_axes_locatable(self.axe0)
cax = divider.new_vertical(size="5%", pad=0.05, pack_start=True)
self.colorbar = self.figure.add_axes(cax)
self.figure.colorbar(self.image, cax=cax, orientation='horizontal')
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
# QApplication.setStyle(QStyleFactory.create("plastique"))
form = FenetrePrincipale()
form.show()
sys.exit(app.exec_())
Operating system: windows 7 Pro
Matplotlib version: 4.0.4
Matplotlib backend: Qt5Agg
Python version: 3.6
Other libraries: PyQt5
Edit 25/10/17 : new code for example
Below is a version of your example script that fixes all the issues. Most of the problems are caused by a very muddled use of layouts. I had to completely
re-write the creation_layout method in order to get a sane starting point so I could see where the problems were. I also temporarily restored the background colour of the canvas to make it easier to see how the widgets are layed out relative to each other. I realize that it won't be easy to incorporate some of my changes into your real code. But hopefully it will give you some ideas on how to simplify your layout structure.
The most important fix is the use of subplots_adjust in the creation_figure method. This removes all the empty space at the top of the canvas, so there is no longer any need to try to position other widgets on top of it.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
class FenetrePrincipale(QWidget):
def __init__(self, parent=None):
super(FenetrePrincipale, self).__init__(parent)
self.setupUi(self)
# Fonction de configuration de la classe
def setupUi(self, Form):
self.Form = Form
Form.setMinimumSize(1220, 850)
self.creation_GUI()
self.creation_figure()
self.creation_layout()
self.tabWidget.setCurrentIndex(0)
self.Bouton_quitter.clicked.connect(self.close)
def resizeEvent(self, QResizeEvent):
self.tabWidget.setMinimumSize(QSize(self.width() - 20, self.height() - 60))
def creation_GUI(self):
self.tabWidget = QTabWidget()
self.tab1 = QWidget()
self.tabWidget.addTab(self.tab1, " Tab1 ")
self.Widget_choixPalette_Label = QLabel(self.tab1)
self.Widget_choixPalette_Label.setText("Text1")
self.Widget_choixPalette_ComboBox = QComboBox(self.tab1)
self.Widget_choixPalette_ComboBox.addItem("Try1")
self.Widget_choixPalette_ComboBox.addItem("Try2")
self.Bouton_quitter = QPushButton(self.tab1)
self.Bouton_quitter.setText("Quit")
def creation_layout(self):
LayoutForm = QGridLayout(self)
LayoutForm.addWidget(self.tabWidget, 0, 0, 1, 1)
LayoutTab1 = QGridLayout(self.tab1)
LayoutTab1.addWidget(self.Widget_choixPalette_Label, 0, 1, 1, 1)
LayoutTab1.addWidget(self.Widget_choixPalette_ComboBox, 1, 1, 1, 1)
self.Widget_choixPalette_ComboBox.setMinimumWidth(200)
LayoutTab1.addWidget(self.canvas, 2, 0, 1, 3)
LayoutTab1.addWidget(self.Bouton_quitter, 2, 3, 1, 1, Qt.AlignRight | Qt.AlignBottom)
LayoutTab1.setRowStretch(2, 1)
LayoutTab1.setColumnStretch(0, 1)
LayoutTab1.setColumnStretch(2, 1)
def creation_figure(self):
# Create figure (transparent background)
self.figure = plt.figure()
# self.figure.patch.set_facecolor('None')
self.canvas = FigureCanvas(self.figure)
self.canvas.setStyleSheet("background-color:transparent;")
# Adding one subplot for image
self.axe0 = self.figure.add_subplot(111)
self.axe0.get_xaxis().set_visible(False)
self.axe0.get_yaxis().set_visible(False)
# plt.tight_layout()
# Data for init image
self.imageInit = [[255] * 320 for i in range(240)]
self.imageInit[0][0] = 0
# Init image and add colorbar
self.image = self.axe0.imshow(self.imageInit, interpolation='none')
divider = make_axes_locatable(self.axe0)
cax = divider.new_vertical(size="5%", pad=0.05, pack_start=True)
self.colorbar = self.figure.add_axes(cax)
self.figure.colorbar(self.image, cax=cax, orientation='horizontal')
plt.subplots_adjust(left=0, bottom=0.05, right=1, top=1, wspace=0, hspace=0)
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
# QApplication.setStyle(QStyleFactory.create("plastique"))
form = FenetrePrincipale()
form.show()
sys.exit(app.exec_())
Just reverse the order you add things to the layout. Add the canvas first, then the button on top
LayoutForm.addWidget(canvas,1,0,1,6)
LayoutForm.addWidget(button,1,0,1,2)
I was under the impression that svg worked like a canvas instruction.
However, I am unable to get the svg to change color. Part of the code below is using the code provided in the sample at Kivy's github.
with self.canvas:
Color(0.3833, 1.0, 0.0)
Entire Code
import sys
from glob import glob
from kivy.uix.scatter import Scatter
from kivy.app import App
from kivy.graphics.svg import Svg
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from random import randint
from kivy.graphics import *
Builder.load_string("""
<SvgWidget>:
do_rotation: False
<FloatLayout>:
id: svg_holder
canvas.before:
Color:
rgb: (1, 1, 1)
Rectangle:
pos: self.pos
size: self.size
""")
class SvgWidget(Scatter):
def __init__(self, filename, **kwargs):
super(SvgWidget, self).__init__(**kwargs)
with self.canvas:
Color(0.3833, 1.0, 0.0)
svg = Svg(filename)
self.size = svg.width, svg.height
def remove(self):
print('remove', self)
class SvgApp(App):
def build(self):
self.root = FloatLayout()
for i in range(6):
svg = SvgWidget('star.svg', size_hint=(None, None))
self.root.add_widget(svg)
svg.scale = randint(1,4)
svg.center = randint(0,Window.width), randint(0,Window.height)
if __name__ == '__main__':
SvgApp().run()
I don't think what you asking for is possible since the Svg contains Color instructions itself, so it overrides yours... Maybe your code can use an overlay with opacity to make the desired effect
I want to draw a few triangles in a canvas, which will resize as I resize the window. The code use is as follow
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
from kivy.uix.stacklayout import StackLayout
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
Builder.load_string("""
<ScreenUI>:
radius: 0.9 * min(self.center_x, self.center_y)
tside: 2 * (min(self.center_x, self.center_y) - self.radius / 1.4)
r_width: self.center_x + self.radius
r_x: self.center_x - self.radius
canvas:
Triangle:
points: root.r_x, 0, root.tside, 0, 0, root.tside
Triangle:
points: root.r_width, 0, root.r_x - root.tside, 0, root.r_width, root.tside
""")
class ScreenUI(BoxLayout):
pass
class WidgetApp(App):
def build(self):
app = ScreenUI()
return app
if __name__ == '__main__':
WidgetApp().run()
The errors I get are:
kivy.lang.BuilderException: Parser: File "<inline>", line 13:
...
11: points: root.r_x, 0, root.tside, 0, 0, root.tside
12: Triangle:
>>13: points: root.r_width, 0, root.r_x - root.tside, 0, root.r_width, root.tside
...
BuilderException: Parser: File "<inline>", line 13:
...
11: points: root.r_x, 0, root.tside, 0, 0, root.tside
12: Triangle:
>>13: points: root.r_width, 0, root.r_x - root.tside, 0, root.r_width, root.tside
...
TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
How can I use just the KV language to compute al the corners of the triangles?
You need to add the properties to the ScreenUI Python class:
class ScreenUI(BoxLayout):
radius = NumericProperty(0)
tside = NumericProperty(0)
r_width = NumericProperty(0)
r_x = NumericProperty(0)
Don't forget to import the NumericProperty:
from kivy.properties import NumericProperty