How to make multiple fbo work in kivy? - python

I'm working on 3d with kivy.I wanted to make bloom effect by postprocessing shader.I dont know how to pass multiple fbo in kivy..i can make one pass but cant make multiple pass..so,can any one help me?
Here is my code:
class SergioGameEngine(Widget):
def __init__(self, **kwargs):
self.canvas = RenderContext(size = self.size,clear_color=(0.5,0.5,0.5,1.0))
with self.canvas:
self.viewport = Rectangle(size=(self.size), pos=self.pos)
self.fbo = Fbo(size=self.size,
with_depthbuffer=True,
compute_normal_mat=True,
clear_color=(0.5, 0.5, 0.5, 1.))
self.fbo.shader.source = resource_find('simple.glsl')
Loading meshes.....
with self.fbo:
self.cb = Callback(self.setup_gl_context)
PushMatrix()
self.setup_scene()
PopMatrix()
self.cb = Callback(self.reset_gl_context)
self.canvas.shader.fs = fsi #this is postprocessing shader
self.pm = Window.render_context['projection_mat']
self.mm = Window.render_context['modelview_mat']
self.canvas['projection_mat'] = self.pm
self.canvas['modelview_mat'] = self.mm
def on_pos(self, instance, value):
self.viewport.pos = (0.0,0.0)
print(value)
def on_texture(self, instance, value):
self.viewport.texture = self.fbo.texture
fsi = """postprocessing shader"""
Now,canvas viewport is updated with fbo texture...
and the viewport texture is used as sampler on canvas fragment shader and i can edit that screen texture ...but the thing is how can i take canvas outputed frametexture and pass again as sampler for reusing as screen shader?
:)

Related

Anchoring widgets in kivy 1.9.0 RelativeLayout

I have created a RelativeLayout subclass that positions its children in a grid by supplying them positions in the code (Python, not kv file). It works, but items are placed some 25 pixels to the upper-right from the position of layout itself, as shown by the canvas block. Python code for Layout subclass:
class RLMapWidget(RelativeLayout):
def __init__(self, map=None, **kwargs):
super(FloatLayout, self).__init__(**kwargs)
# Connecting to map, factories and other objects this class should know about
self.tile_factory = TileWidgetFactory()
self.map = map
# Initializing tile widgets for BG layer and adding them as children
for x in range(self.map.size[0]):
for y in range(self.map.size[1]):
tile_widget = self.tile_factory.create_tile_widget(self.map.get_item(layer='bg',
location=(x, y)))
# tile_widget.pos = (50*x, 50*y)
tile_widget.pos = self._get_screen_pos((x, y))
self.add_widget(tile_widget)
# Initializing widgets for actor layers
for x in range(self.map.size[0]):
for y in range(self.map.size[1]):
if self.map.has_item(layer='actors', location=(x, y)):
actor_widget = self.tile_factory.create_actor_widget(self.map.get_item(layer='actors',
displayed location=(x, y)))
actor_widget.pos=(50*x, 50*y)
self.add_widget(actor_widget)
# Map background canvas. Used solely to test positioning
with self.canvas.before:
Color(0, 0, 1, 1)
self.rect = Rectangle(size = self.size, pos=self.pos)
self.bind(pos=self.update_rect, size=self.update_rect)
# Initializing keyboard bindings and key lists
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_key_down)
# The list of keys that will not be ignored by on_key_down
self.used_keys=['w', 'a', 's', 'd']
def redraw_actors(self):
for actor in self.map.actors:
actor.widget.pos = self._get_screen_pos(actor.location)
def _get_screen_pos(self, location):
"""
Return screen coordinates (in pixels) for a given location
:param location: int tuple
:return: int tuple
"""
return (location[0]*50, location[1]*50)
# Keyboard-related methods
def _on_key_down(self, keyboard, keycode, text, modifiers):
"""
Process keyboard event and make a turn, if necessary
:param keyboard:
:param keycode:
:param text:
:param modifiers:
:return:
"""
if keycode[1] in self.used_keys:
self.map.process_turn(keycode)
self.redraw_actors()
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_key_down)
self._keyboard = None
def update_rect(self, pos, size):
self.rect.pos = self.pos
self.rect.size = self.size
class CampApp(App):
def build(self):
root = FloatLayout()
map_factory = MapFactory()
map = map_factory.create_test_map()
map_widget = RLMapWidget(map=map,
size=(map.size[0]*50, map.size[1]*50),
size_hint=(None, None))
root.add_widget(map_widget)
return root
if __name__ == '__main__':
CampApp().run()
Factory class that makes tiles:
class TileWidgetFactory(object):
def __init__(self):
pass
def create_tile_widget(self, tile):
tile.widget = Image(source=tile.image_source,
size_hint=(None, None))
return tile.widget
def create_actor_widget(self, actor):
actor.widget = Image(source='Tmp_frame_black.png',
size_hint=(None, None))
return actor.widget
Okay, solved it myself. It turns out that if I supply the size to child widgets in factory, they are positioned properly. Although it solves the problem I have, I'd still be grateful if someone can explain where this quirk does come from.

Rendering text to a kivy canvas

I am trying to draw my own graphic within a kivy 'canvas'. For now I have a red or green rectangle which changes colour once per second, but I want to add a changing text label.
After a little searching it appears that there isn't a "Text" Instruction which can be added to the canvas. I have found a few references to using a Label() widget as well as the canvas Instructions, but this does not seem ideal, and also I can't seem to get it to render more than once.
Here's my object as it stands at the moment:
class HVObject(BoxLayout):
def __init__(self, **kwargs):
BoxLayout.__init__(self, **kwargs)
self.colour = 1
self.label = Label()
self.render()
self.add_widget(self.label)
self.bind(size=self._update_rect, pos=self._update_rect)
Clock.schedule_interval(self.callevery, 1)
def render(self):
self.canvas.clear()
self.rect = Rectangle(size=self.size, pos=self.pos)
self.canvas.add(Color(1-self.colour, self.colour, 0, 1))
self.canvas.add(self.rect)
self.label.text = "COL %d" % self.colour
self.canvas.ask_update()
def callevery(self, x):
self.colour = 1-self.colour
self.render()
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
self.label.pos = instance.pos
Is there an easy way to achieve the effect I need?
Thank you
Answering my own question:
After a little look around the [kivy] garden, I found Tickline (and Tick). and the use of CoreLabel() and Rectangle(texture=...)
Here's my updated render() method which adds the text object I need.
def render(self):
self.canvas.clear()
self.canvas.add(Color(1-self.colour, self.colour, 0, 1))
self.rect = Rectangle(size=self.size, pos=self.pos)
self.canvas.add(self.rect)
label = CoreLabel(text="COL %d" % self.colour, font_size=20)
label.refresh()
text = label.texture
self.canvas.add(Color(self.colour, 1-self.colour,0, 1))
pos = list(self.pos[i] + (self.size[i] - text.size[i]) / 2 for i in range(2))
self.canvas.add(Rectangle(size=text.size, pos=pos, texture=text))
self.canvas.ask_update()
Which works for me, albeit a little clunky!

Nesting widgets in kivy

I'm trying to make an interface in kivy and I think there are some fundamental things I don't understand about custom widgets and how to hierarchy them, even after going through the tutorial. I think I have more of a box-model html mindset, so the way widgets are nested in native GUIs are still kind of foreign to me.
Some background:
I consulted this entry on how to add a background (It answers the question: "How to add a background image/color/video/... to a Layout", which I believe I was replicating with the _update_rect methods).
This one that has an on_touch_down event.
K, I'm trying to get MyApp to look like this...
As I understand it, here are the things I'd need for that:
You have an app.
The app has a root.
The root has a background, say the background is white.
The background contains a holder, say the holder has a little margin from the background and is gray. I do want this to be a widget rather than just a non-widget canvas because I want the holder to react to click events, as well. It turns random colors when clicked. (Just to show it's doing something.)
The holder contains two custom widgets. These are circles with labels, each colored green. They turn random colors when clicked (just to show they're doing something).
Here's my code, which doesn't crash anymore, but doesn't display any colors or objects of any kind.
#!/usr/bin/kivy
import kivy
kivy.require('1.7.2')
from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Color, Ellipse, Rectangle
class MyApp(App):
title = 'My App'
def build(self):
root = RootWidget()
root.bind(
size=self._update_rect,
pos=self._update_rect)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
with self.canvas.before:
Color(1, 0, 0, 1) # This RED does not display.
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="some junk!") # This label does not display.
mybackground = Background()
self.add_widget(mybackground)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class Background(Widget):
def __init__(self, **kwargs):
super(Background, self).__init__(**kwargs)
with self.canvas.before:
Color(1, 1, 1, 1) # This WHITE does not display
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="More stuff!") # This label does not display.
myholder = Holder()
self.add_widget(myholder)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class Holder(Widget):
def __init__(self, **kwargs):
super(Holder, self).__init__(**kwargs)
with self.canvas.before:
Color(0.25, 0.25, 0.25, 1) # This GRAY does not display
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="More stuff!") # This does not display.
c1 = Circley(label="Label 1") # I see I'd need to do some size/pos math here to center
c2 = Circley(label="Label 2") # but since everything isn't working, I've tabled this.
self.add_widget(c1)
self.add_widget(c2)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
def on_touch_down(self, touch):
rcolor = Color(random(), random(), random(), 1)
with self.canvas:
self.color = rcolor
class Circley(Widget):
def __init__(self, label='label', **kwargs):
super(Circley, self).__init__(**kwargs)
with self.canvas.before:
Color(0, 1, 0, 1) # This GREEN does not display
self.circ = Ellipse(
size=self.size,
pos=self.pos,
text=label
)
def _update_circ(self, instance, value):
self.circ.pos = instance.pos
self.circ.size = instance.size
def on_touch_down(self, touch):
rcolor = Color(random(), random(), random(), 1)
with self.canvas:
self.color = rcolor
if __name__ == '__main__':
MyApp().run()
Any pointers on what I'm doing wrong and how to nest these widgets correctly?
The reason you get a blank screen is that your app's build() method does not return anything. Whatever it returns would be the root widget, but even though you make some widgets you don't return one so nothing is displayed.
If you fix this, you can run the app again but you'll immediately get an error something like MyApp has no attribute rect. This is because your root widget is immediately sized and positioned to fill the window, which (as per your root.bind line) triggers MyApp._update_rect. However, this method try to modify MyApp.rect.pos...but the app doesn't have a self.rect! You presumably intended to bind to root._update_rect, not the app's method. (Edit: I bound to root._update_rect instead and now at least the red background and green circle do appear.)
Edit: And as a side note, this would be a lot easier using the kv language, which could automatically take care of a lot of the widget creation, rectangle binding etc.
I don't have time to fix it all right now, but perhaps these two problems can help you fix the overall flow. I'll try to post a more complete answer later if nobody else has.
Here's an updated MyApp, as per the comments.
class MyApp(App):
title = 'My App'
def build(self):
root = RootWidget()
root.bind(
size=root._update_rect,
pos=root._update_rect)
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.graphics import Color, Rectangle, Ellipse
class MyApp(App):
title = 'My App'
def build(self):
root = RootWidget()
root.bind(
size=root._update_rect,
pos=root._update_rect)
return root
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
with self.canvas.before:
Color(1, 0, 0, 1) # This RED does not display.
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="some junk!") # This label does not display.
mybackground = Background()
self.add_widget(mybackground)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class Background(Widget):
def __init__(self, **kwargs):
super(Background, self).__init__(**kwargs)
with self.canvas.before:
Color(1, 1, 1, 1) # This WHITE does not display
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="More stuff!") # This label does not display.
myholder = Holder()
self.add_widget(myholder)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class Holder(Widget):
def __init__(self, **kwargs):
super(Holder, self).__init__(**kwargs)
with self.canvas.before:
Color(0.25, 0.25, 0.25, 1) # This GRAY does not display
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="More stuff!") # This does not display.
c1 = Circley(label="Label 1") # I see I'd need to do some size/pos math here to center
c2 = Circley(label="Label 2") # but since everything isn't working, I've tabled this.
self.add_widget(c1)
self.add_widget(c2)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
def on_touch_down(self, touch):
rcolor = Color(random(), random(), random(), 1)
with self.canvas:
self.color = rcolor
class Circley(Widget):
def __init__(self, label='label', **kwargs):
super(Circley, self).__init__(**kwargs)
with self.canvas.before:
Color(0, 1, 0, 1) # This GREEN does not display
self.circ = Ellipse(
size=self.size,
pos=self.pos,
text=label
)
def _update_circ(self, instance, value):
self.circ.pos = instance.pos
self.circ.size = instance.size
def on_touch_down(self, touch):
rcolor = Color(random(), random(), random(), 1)
with self.canvas:
self.color = rcolor
if __name__ == '__main__':
MyApp().run()

PyQt - displaying widget on top of widget

I'm making an application that shows a map of an area and I'm trying to draw nodes on top of it which can represent information.
I made it all work, but did so simply by making one custom widget which I showed and printing everything again and again everytime information changed. Also I couldn't 'connect' the nodes to listeners, because they were just images in the original widget.
This made me want to reform my GUI and now I'm trying to make every class a custom widget! But there's a problem, my MapNodes aren't showing up anymore.
I searched stackoverflow and found this helpful thread:
How to set absolute position of the widgets in qt
So I have to give my mapnodes a parent, and parent = the widget that is being shown (?)
Anyway, here is my throw at it, pasting the relevant code here. Hint at where stuff might go horrible wrong: all the inits
app = QtGui.QApplication(list())
mutexbranch = Lock()
mutexnode = Lock()
def exec():
return app.exec_()
#Singleton Pattern: wanneer en object aan iets moet kunnen
# waar het inherent door de structuur niet aankon
# wordt dit via dit singleton opgelost
class GuiInternalCommunication:
realmap = 0
class MapView(QtGui.QWidget, listener.Listener):
def __init__(self, mapimagepath):
QtGui.QMainWindow.__init__(self)
listener.Listener.__init__(self)
self.map = Map(self, mapimagepath)
#self.setCentralWidget(self.map)
self.initUI()
def initUI(self):
self.setWindowTitle('Population mapping')
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.map)
self.setLayout(hbox)
resolution = QtGui.QDesktopWidget().screenGeometry()
self.setGeometry(20,20,550,800)
self.show()
######################################################################
class Map(QtGui.QWidget):
def __init__(self, parent, mapimagepath):
QtGui.QWidget.__init__(self, parent)
#self.timer = QtCore.QBasicTimer()
#coordinaten hoeken NE en SW voor kaart in map graphics van SKO
self.realmap = RealMap(
mapimagepath,
(51.0442, 3.7268),
(51.0405, 3.7242),
550,
800)
GuiInternalCommunication.realmap = self.realmap
self.needsupdate = True
self.timelabel = 0
parent.setGeometry(0,0,self.realmap.width, self.realmap.height)
self.mapNodes = {}
self.mapBranches = {}
def paintEvent(self, event):
painter = QtGui.QPainter()
painter.begin(self)
rect = self.contentsRect()
#teken achtergrond
self.realmap.drawRealMap(painter)
#teken branches
mutexbranch.acquire()
try:
for branch, mapBranch in self.mapBranches.items():
mapBranch.drawMapBranch(painter)
finally:
mutexbranch.release()
######################################################################
class RealMap(QtGui.QWidget):
def __init__(self, path, coordRightTop, coordLeftBot, width, height, pixpermet = 2.6):
super(RealMap, self).__init__()
self.path = path
self.mapimage = QtGui.QImage(self.path)
self.coordLeftBot = coordLeftBot
self.coordRightTop = coordRightTop
self.width = width
self.height = height
self.realdim = self.calcRealDim()
self.pixpermet = pixpermet
def paintEvent(self, e):
painter = QtGui.QPainter()
painter.begin(self)
self.drawRealMap(self, painter)
painter.end()
def drawRealMap(self, painter):
painter.drawImage(0,0,self.mapimage)
######################################################################
class MapNode(QtGui.QWidget):
dangertocolor = {"normal":"graphics//gradients//green.png",
"elevated":"graphics//gradients//orange.png",
"danger":"graphics//gradients//red.png"}
gradimage = {"normal":QtGui.QImage(dangertocolor["normal"]),
"elevated":QtGui.QImage(dangertocolor["elevated"]),
"danger":QtGui.QImage(dangertocolor["danger"])}
btimage = QtGui.QImage("graphics//BT-icon.png")
def __init__(self, scanner, x, y, danger = 0, parent = None):
# MapNode erft over van QWidget
super(MapNode, self).__init__()
QtGui.QWidget.__init__(self, parent)
self.scanner = scanner
self.x = x
self.y = y
self.danger = 'normal'
self.calcDanger(danger)
self.grads = {}
self.grad = QtGui.QImage(MapNode.dangertocolor[self.danger])
def paintEvent(self, e):
painter = QtGui.QPainter()
painter.begin(self)
self.drawMapNode(painter)
painter.end()
def drawMapNode(self, painter):
realmap = GuiInternalCommunication.realmap
radiusm = self.scanner.range
radiusp = radiusm*realmap.pixpermet
factor = radiusp/200 # basis grootte gradiƫnten is 200 pixels.
grad = MapNode.gradimage[self.danger]
grad = grad.scaled(grad.size().width()*factor, grad.size().height()*factor)
painter.drawImage(self.x-100*factor,self.y-100*factor, grad)
painter.drawImage(self.x-10, self.y-10,MapNode.btimage)
painter.drawText(self.x-15, self.y+20, str(self.scanner.sensorid) + '-' + str(self.scanner.name))
######################################################################
class MapBranch:
branchpens = {"normal": QtGui.QPen(QtCore.Qt.green, 3, QtCore.Qt.DashLine),
"elevated": QtGui.QPen(QtGui.QColor(255, 51, 0), 3, QtCore.Qt.DashLine), #mandarine orange hex is 255-165-0
"danger": QtGui.QPen(QtCore.Qt.red, 3, QtCore.Qt.DashLine)}
def __init__(self, branch, mapnode1, mapnode2, danger = 0):
self.mapnode1 = mapnode1
self.mapnode2 = mapnode2
self.branch = branch
self.danger = danger
self.calcDanger(danger)
def drawMapBranch(self, painter):
painter.setPen(MapBranch.branchpens[self.danger])
painter.drawLine(self.mapnode1.x,
self.mapnode1.y,
self.mapnode2.x,
self.mapnode2.y)
EDIT - I forgot to add the code that adds the nodes. So after an event comes in the node needs to be created, this method fires creating the node:
def addNode(self, scanner):
mutexnode.acquire()
try:
coord = self.realmap.convertLatLon2Pix((scanner.latitude, scanner.longitude))
self.mapNodes[scanner.sensorid] = MapNode(scanner, coord[0], coord[1], parent = self)
self.mapNodes[scanner.sensorid].move(coord[0],coord[1])
#self.mapNodes[scanner.sensorid].show()
finally:
mutexnode.release()
I would recommend you to use the QGraphicsScene and QGraphicsItem classes for your map instead of normal QWidget classes, since they are made exactly for the purpose of displaying a large number of graphical items:
QGraphicsScene http://doc.qt.io/qt-5/qgraphicsscene.html
QGraphicsItem: http://doc.qt.io/qt-5/qgraphicsitem-members.html
From the documentation:
The QGraphicsScene class provides a surface for managing a large number of 2D graphical items.
The class serves as a container for QGraphicsItems. It is used together with QGraphicsView for visualizing graphical items, such as lines, rectangles, text, or even custom items, on a 2D surface. QGraphicsScene is part of the Graphics View Framework.
QGraphicsScene also provides functionality that lets you efficiently determine both the location of items, and for determining what items are visible within an arbitrary area on the scene. With the QGraphicsView widget, you can either visualize the whole scene, or zoom in and view only parts of the scene.
You can also embed widgets derived from QWidget in the scene, which should allow you to display practically any kind of information. As a bonus, you will get layering, fast transformations and ready-to-use handling of mouse interactions, which should be quite useful for realizing an interactive map.

Need help working with self.MemoryDC in wxPython

I'm trying to make a custom text widget that is double buffered (In order to avoid flicker).
However, I'd like to be able to do a few things. Yet, I'm unsure of the exact methods I should use.
The first two are easy I simply want to change the background and foreground color.
So more or less I want to be able to change the text color for self.Text in self.Draw().
Snippet:
self.Text = mdc.DrawText(self.TextString, 10, 0)
As sell as the Background (fill) color for self.MemoryDC.
Next, does anyone know how I could center self.Text? Finally, how do I configure self.Text after it has been created?
The widget thus far:
class DynamicText (wx.Panel):
def __init__(self, par):
self.Par = par
wx.Panel.__init__(self, self.Par)
self.Time = Time(self, func=self.SetTime)
self.Dim = self.Par.GetClientSize()
self.SetSize(self.Dim)
self.Bind(wx.EVT_SIZE, self.Resize)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.Erase)
self.Bind(wx.EVT_PAINT, self.Paint)
def Set (self, text) :
self.TextString = text
def SetTime (self, time) :
self.Set(str(time))
self.Resize(None)
def Resize(self, event):
self.Width, self.Height = self.GetSize()
bitmap = wx.EmptyBitmap(self.Width, self.Height)
self.MemoryDC = wx.MemoryDC(bitmap)
''' Redraws **self.MemoryDC** '''
mdc = self.MemoryDC
''' Deletes everything from widget. '''
mdc.Clear()
fs = 11
font = wx.Font( fs, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
mdc.SetFont(font)
self.Draw()
self.Refresh()
def Draw (self) :
mdc = self.MemoryDC
self.Text = mdc.DrawText(self.TextString, 10, 0)
def Erase(self, event):
''' Does nothing, as to avoid flicker. '''
pass
def Paint(self, event):
pdc = wx.PaintDC(self)
w, h = self.MemoryDC.GetSize()
pdc.Blit(0, 0, w, h, self.MemoryDC, 0, 0)
I don't understand what you mean by configuring self.Text after it was created. If you want to change the text after you've drawn it - you can't. Once you've drawn it to the DC it's there, and the only way to change it would be to clear the DC and repaint it. In your case, it seems all you need to do when the text is updated is to call Resize() again, forcing a redraw. Note that DrawText() retruns nothing, so the value of your self.Text would be None. You definitely can't use that to refer to the drawn text. :D
As for the rest, here's an example of a Draw() method that centers the text and paints it blue:
def Draw(self) :
mdc = self.MemoryDC
dc_width, dc_height = mdc.GetSizeTuple()
text_width, text_height, descent, externalLeading = mdc.GetFullTextExtent(self.TextString)
x = (dc_width - text_width) / 2
y = (dc_height - text_height) / 2
mdc.SetTextForeground('Blue')
mdc.DrawText(self.TextString, x, y)

Categories