I am relatively new to Python and Pyglet, I am trying to create an app that dynamically displays photos accordingly to the ID of a command sent via serial.
Here is my code:
import pyglet
from pyglet import clock
import serial
import json
import os
base_dir = 'data'
data = []
currentDatum = ''
def initialiseData():
global data
#load the json
with open('dataset.json') as f:
data = json.load(f)
#for every file in the json load the image
for d in data:
d['media'] = pyglet.image.load(os.path.join(base_dir, d['name']))
print("Scan a tag")
def readSerial(dt):
global currentDatum
tag = ser.readline()
tag = tag.strip()
for d in data:
if d['id'] == tag:
currentDatum = d
print(currentDatum)
ser = serial.Serial('/dev/cu.usbmodem1421', 9600)
initialiseData()
window = pyglet.window.Window(1000, 800, resizable = True)
#window.event
def on_draw():
window.clear()
currentDatum['media'].anchor_x = currentDatum['media'].width/2 - window.width/2
currentDatum['media'].anchor_y = currentDatum['media'].height/2 - window.height/2
currentDatum['media'].blit(0, 0)
clock.schedule(readSerial)
pyglet.app.run()
The application works fine, in the sense that it loads the data from the son and when I send the serial ID it gets read instantly, but every mouse interaction freezes the app: I cannot close the window, cannot resize it, it just gets stuck. Any advice?
I have no idea what the program actually does, or how to use it.
And since I can't replicate your environment (you've got a usbmodem for instance), I'll try my best approach at a generic solution that should work and plan ahead for future development on your project:
import pyglet
from pyglet.gl import *
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.x, self.y = 0, 0
self.batch = pyglet.graphics.Batch()
self.data = []
self.currentDatum = ''
self.ser = serial.Serial('/dev/cu.usbmodem1421', 9600)
self.initialiseData()
pyglet.clock.schedule(self.readSerial)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_motion(self, x, y, dx, dy):
pass
def on_mouse_release(self, x, y, button, modifiers):
pass
def on_mouse_press(self, x, y, button, modifiers):
pass
def on_mouse_drag(self, x, y, dx, dy, button, modifiers):
pass
def on_key_release(self, symbol, modifiers):
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
# For future reference, use batches:
self.batch.draw()
# But this is how it's implemented atm:
self.currentDatum['media'].anchor_x = self.currentDatum['media'].width/2 - self.width/2
self.currentDatum['media'].anchor_y = self.currentDatum['media'].height/2 - self.height/2
self.currentDatum['media'].blit(0, 0)
self.flip()
def initialiseData(self):
#load the json
with open('dataset.json') as f:
self.data = json.load(f)
#for every file in the json load the image
for d in self.data:
d['media'] = pyglet.image.load(os.path.join(base_dir, d['name']))
print("Scan a tag")
def readSerial(self, dt):
tag = self.ser.readline()
tag = tag.strip()
for d in self.data:
if d['id'] == tag:
self.currentDatum = d
print(self.currentDatum)
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main()
x.run()
It's bound to be a few run time errors in this code, syntax that I've might have messed up. But I've ported as much of your code as possible into a object oriented way of thinking. Aka a class that inherits the Window class in order for you to modify elements within and the window itself.
There's also some placeholder-functions that you can use to handle mouse and keyboard events. These are inherited/overlapping the pyglet.window.Window class. So any function supported by Window can be put into this class.
There's also a custom pyglet.app.run() that handles the event polling, making sure you don't get stuck on keyboard, mouse or window (resize, move etc) events.
Hope this works.
Related
I've been trying to create a small app that lets me draw over the screen.
I think I'm close in that if I don't have the line that sets the background to clear then it creates an opaque grey window and lets me draw on the screen. But if I set the background to clear then the window doesn't seem to be created at all, no errors, but initWithFrame() and drawRect_() are each called once. (I don't think it's that it's created but just clear - it doesn't receive mouse events, mission control doesn't think there's a window).
from AppKit import NSApplication, NSWindow, NSView, NSEvent, NSColor, NSPoint, NSWindowStyleMaskTitled, NSWindowStyleMaskClosable, NSBackingStoreBuffered, NSWindowStyleMaskBorderless, NSMainMenuWindowLevel, NSKeyDownMask, NSApplicationActivationPolicyRegular, NSScreen, NSColor, NSBezierPath
import objc
import signal
class DrawingView(NSView):
def initWithFrame_(self, frame):
self = super().initWithFrame_(frame)
if self:
self.last_point = objc.NULL
self.last_drawn_point = objc.NULL
self.points = []
return self
def drawRect_(self, rect):
if self.last_point is not objc.NULL:
if self.last_drawn_point is objc.NULL:
self.last_drawn_point = self.last_point
path = NSBezierPath.bezierPath()
path.moveToPoint_(self.points[0])
for point in self.points:
path.lineToPoint_(point)
NSColor.redColor().set()
path.stroke()
self.last_drawn_point = self.last_point
def mouseDown_(self, event):
self.last_point = event.locationInWindow()
self.last_drawn_point = event.locationInWindow()
def mouseDragged_(self, event):
self.last_point = event.locationInWindow()
self.points.append(event.locationInWindow())
self.setNeedsDisplay_(True)
def mouseUp_(self, event):
print("mouseUp")
def awakeFromNib(self):
self.setAcceptsTouchEvents_(True)
self.setWantsRestingTouches_(False)
def key_handler(event):
if event.keyCode() == 53:
NSApplication.sharedApplication().terminate_(None)
def signal_handler(sig, frame):
NSApplication.sharedApplication().terminate_(None)
def main():
signal.signal(signal.SIGINT, signal_handler)
app = NSApplication.sharedApplication()
app.setActivationPolicy_(NSApplicationActivationPolicyRegular)
screen = NSScreen.mainScreen()
screen_frame = screen.frame()
window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
((0, 0), screen_frame.size),
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable,
NSBackingStoreBuffered,
False
)
window.setStyleMask_(NSWindowStyleMaskBorderless)
window.setLevel_(NSMainMenuWindowLevel + 1)
window.setOpaque_(True)
window.setContentView_(DrawingView.alloc().initWithFrame_(window.frame()))
window.setBackgroundColor_(NSColor.clearColor())
window.makeKeyAndOrderFront_(None)
window.setAcceptsMouseMovedEvents_(True)
NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(NSKeyDownMask, key_handler)
app.activateIgnoringOtherApps_(True)
app.run()
if __name__ == '__main__':
main()
I coded a game using pylet. It uses a static window with width 1600 and height 900 assuming users have a fullHD display so everything will be visible. However on some devices (with small displays) the window is way bigger as expected. I figured out that the pixel_ratio is set up (for example to 2.0) making each virtual pixel to be displayed double size (2x2) in physical pixel.
I want to prevent this behavior but can't figure out how, I know I can get the pixel ratio easily by get_pixel_ratio() but I actually don't know how to set them or prevent pyglet from automatically setting them.
I also tried to use glViewport which seemed to have an effect but it didn't worked the way I wanted.
So how can I change the pixel_ratio or prevent changing it automatically.
Asked around in the official discord server for information, as I tried to reproduce the issue myself with some code, and this is what I used to test it:
import math
from pyglet import *
from pyglet.gl import *
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.x, self.y = 0, 0
self.keys = {}
verts = []
for i in range(30):
angle = math.radians(float(i)/30 * 360.0)
x = 100*math.cos(angle) + 300
y = 100*math.sin(angle) + 200
verts += [x,y]
self.pixel_ratio = 100
self.circle = pyglet.graphics.vertex_list(30, ('v2f', verts))
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_release(self, symbol, modifiers):
try:
del self.keys[symbol]
except:
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
self.keys[symbol] = True
def render(self):
self.clear()
glClear(pyglet.gl.GL_COLOR_BUFFER_BIT)
glColor3f(1,1,0)
self.circle.draw(GL_LINE_LOOP)
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main()
x.run()
Not that the code mattered heh, since the variable pixel_ratio is just, and I quote: "indicating that the actual number of pixels in the window is larger than the size of the Window created."
This is something OSX does to cope with the high DPI, using this information you should be able to scale your graphics accordingly. Window.get_framebuffer_size() will show you the difference in requested Window size and Framebuffer size, if any.
So your only way to actually scale up, would be to use glScale or if you're using sprites you can use Sprite.scale to scale the image-data. If you're using 2D graphics I'd go with the sprite option as it's pretty easy to work with.
My custom mouse image is not showing up in pyglet. It loads normally(When you create the window outside a class), but when i make the window in a class and try yo add the custom mouse cursor nothing happens
class x:
def __init__(self):
self.game_window = pyglet.window.Window()
self.on_mouse_press = self.game_window.event(self.on_mouse_press)
self.game_cursor = pyglet.image.load('image.png')
self.cursor = pyglet.window.ImageMouseCursor(self.game_cursor, 50, 70)
self.game_window.set_mouse_cursor(self.cursor)
I have tried printing every one of the lines of code (for mouse image loading)
When i print - self.game_cursor = pyglet.image.load('image.png') - This is the result:
ImageData 85x82
When i print - self.cursor = pyglet.window.ImageMouseCursor(self.game_cursor, 50, 70) - This is the result:
pyglet.window.ImageMouseCursor object at 0x7f4dad76b390
When i print - self.game_window.set_mouse_cursor(self.cursor) - This is the result:
None
How do I fix make the mouse show?
I strongly suggest you inherit the window class in to your class instead of keeping it as a internal value if possible. So you can use override hooks to replace the on_ functions. Perhaps this is an outdated approach, but for the time I've learned to work with these things - it's reommended.
class x(pyglet.window.Window):
def __init__(self):
super(x, self).__init__()
self.game_cursor = pyglet.image.load('image.png')
self.cursor = pyglet.window.ImageMouseCursor(self.game_cursor, 50, 70)
self.set_mouse_cursor(self.cursor)
def on_mouse_press():
# Your code handling on_mouse_press
Here's a working example:
from pyglet import *
from pyglet.gl import *
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.game_cursor = pyglet.image.load('image.png')
self.cursor = pyglet.window.ImageMouseCursor(self.game_cursor, 50, 70)
self.set_mouse_cursor(self.cursor)
self.x, self.y = 0, 0
self.keys = {}
self.mouse_x = 0
self.mouse_y = 0
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_motion(self, x, y, dx, dy):
self.mouse_x = x
def on_key_release(self, symbol, modifiers):
try:
del self.keys[symbol]
except:
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
self.keys[symbol] = True
def render(self):
self.clear()
## Add stuff you want to render here.
## Preferably in the form of a batch.
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main()
x.run()
According to pyglet documentation for ImageMouseCursor class link method draw(x, y) is abstract. So I've tried sub-classing ImageMouseCursor and implementing draw method like this:
import pyglet
pyglet.resource.path = ['resources']
pyglet.resource.reindex()
# subclass definition
class GameMouse(pyglet.window.ImageMouseCursor):
# class initialization
def __init__(self):
self.game_cursor = pyglet.resource.image('game_cursor.png')
super().__init__(self.game_cursor, 0, 34)
# method override
def draw(self, x, y):
self.game_cursor.blit(x, y)
However, this will not work if gl blending is not enabled. gl blending is needed to display alpha channels. I've managed to enable it by sub-classing Window class, and using glEnable / glBlendFunc functions. This is the part of the code that does as described:
# subclass definition:
class GameApp(pyglet.window.Window):
# class initialization
def __init__(self):
super(GameApp, self).__init__()
pyglet.gl.glEnable(pyglet.gl.GL_BLEND)
pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA,
pyglet.gl.GL_ONE_MINUS_SRC_ALPHA)
Hope this helps
class Viewer:
def __init__(self, width, height, arm_info, point_info, point_l):
self.list_x = [1,2,3,4]
self.list_y = [5,6,7,8]
self.i = 0
self.arm_info = arm_info
self.point_info = point_info
def on_key_press(self,symbol,modifiers):
if symbol == pyglet.window.key.Z:
self.point_info[:] = [self.list_x[self.i],self.list_y[self.i]]
self.i += 1
Here to update the point_info[:] I have to press the 'Z' everytime, I just want Z to be pressed once to update point_info[:] in every 1 second .
def on_key_press(self,symbol,modifiers):
if symbol == pyglet.window.key.Z:
for i in range(0,4):
self.point_info[:] = [self.list_x[i],self.list_y[i]]
time.sleep(1)
I have tried above but it doesn't work. How can I do this?
Here is the complete code, the render method is called from another module.
class Viewer(pyglet.window.Window):
def __init__(self, width, height, arm_info, point_info, point_l, mouse_in):
self.list_x = [100,150,200,300,400]
self.list_y = [100,150,200,300,400]
#initialisation of variables
def render(self):
pyglet.clock.tick()
self._update_arm()
self.switch_to()
self.dispatch_events()
self.dispatch_event('on_draw')
self.flip()
def on_draw(self):
#draws on the screen
def _update_arm(self):
#updates the coordinates of the arm , as it moves
def on_key_press(self, symbol, modifiers):
#HERE LIES THE PROBLEM
if symbol == pyglet.window.key.S:
for j in range(0,4):
self.point_info[:] = [self.list_x[j],self.list_y[j]]
print(self.point_info)
#below 2 lines are for drawing on the screen.
self.clear()
self.batch.draw()
j=+1
time.sleep(1)
I created a small runnable example code, on how to achieve this. The point is, that sleep() blocks the program flow. With pyglet, you have a convenient way to schedule future executions with the pyglet.clock.schedule* methods. You can use that, to call a specific function in the future.
Note: Actual, piglet is running a main loop in it's framework. If you hold the program at some position (like you do with sleep()), no further code can be executed meanwhile, therefore, no drawing can happen, if pyglet requires to call some required methods around the draw call. I guess, you are not supposed to call the on_draw() method by yourself.
import pyglet
from pyglet.window import key
from pyglet import clock
import random
window = pyglet.window.Window()
label = pyglet.text.Label('Hello, world',
font_name='Times New Roman',
font_size=36,
x=window.width//2, y=window.height//2,
anchor_x='center', anchor_y='center')
def updatePoint(dt):
label.x = random.random() * window.width//2
label.y = random.random() * window.height//2
#window.event
def on_key_press(symbol, modifiers):
if symbol == key.S:
print('The "S" key was pressed.')
for j in range(0,4):
clock.schedule_once(updatePoint, j)
#window.event
def on_draw():
window.clear()
label.draw()
if __name__ == '__main__':
pyglet.app.run()
Me and my colleagues are writing a data processing application in python.
We are currently working on the frontend part of the application.
We have a big problem though, that's that the application gets the following error after a random amount of time:
QWidget::repaint: Recursive repaint detected
This one also pops up from time to time:
QPainter::begin: Paint device returned engine == 0, type: 1
This is the file where all gui related stuff happens, I cut out the irrelevant methods for the sake of not being to lengthy:
gfx.py:
import sys, random, math
from PyQt4 import QtGui, QtCore
from random import randrange
from eventbased import listener
app = QtGui.QApplication(sys.argv)
def exec():
return app.exec_()
class MapView(QtGui.QMainWindow, listener.Listener):
def __init__(self, mapimagepath = 0, nodes = 0):
QtGui.QMainWindow.__init__(self)
listener.Listener.__init__(self)
self.setWindowTitle('Population mapping')
self.map = Map(self, mapimagepath)
self.setCentralWidget(self.map)
self.map.start()
self.center()
def center(self):
screen = QtGui.QDesktopWidget().screenGeometry()
size = self.geometry()
self.move(50, 0)
def handle(self, event):
if(event.type == 0):
self.map.addNode(event.object.scanner)
if(event.type == 1):
self.map.delNode(event.object.scanner)
if(event.type == 2):
self.map.addBranch(event.object.node1.scanner, event.object.node2.scanner)
if(event.type == 3):
self.map.delBranch(event.object.node1.scanner, event.object.node2.scanner)
if(event.type == 4):
self.map.changeNode(event.object.scanner.sensorid, event.result)
if(event.type == 5):
self.map.changeBranch(event.object.node1.scanner.sensorid, event.object.node2.scanner.sensorid, event.result)
self.repaint(self.map.contentsRect())
self.update(self.map.contentsRect())
######################################################################
class Map(QtGui.QFrame):
def __init__(self, parent, mapimagepath):
QtGui.QFrame.__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)
parent.setGeometry(0,0,self.realmap.width, self.realmap.height)
self.refreshspeed = 5000
self.mapNodes = {}
def addNode(self, scanner):
coord = self.realmap.convertLatLon2Pix((scanner.latitude, scanner.longitude))
self.mapNodes[scanner.sensorid] = MapNode(scanner, coord[0], coord[1])
# type: 4 --> changenode ,
#((change, gem_ref, procentuele verandering ref), scanner object)
def changeNode(self, sensorid, branchdata):
self.mapNodes[sensorid].calcDanger(branchdata[2])
def paintEvent(self, event):
painter = QtGui.QPainter(self)
rect = self.contentsRect()
#teken achtergrond
self.realmap.drawRealMap(painter)
#teken nodes
for sensorid, mapNode in self.mapNodes.items():
mapNode.drawMapNode(painter, self.realmap)
######################################################################
class RealMap:
def __init__(self, path, coordRightTop,
coordLeftBot, width, height, pixpermet = 2.6):
self.path = path
self.coordLeftBot = coordLeftBot
self.coordRightTop = coordRightTop
self.width = width
self.height = height
self.realdim = self.calcRealDim()
self.pixpermet = pixpermet
def drawRealMap(self, painter):
image = QtGui.QImage(self.path)
painter.drawImage(0,0,image)
######################################################################
class MapNode:
dangertocolor = {"normal":"graphics//gradients//green.png",
"elevated":"graphics//gradients//orange.png",
"danger":"graphics//gradients//red.png"}
def __init__(self, scanner, x, y, danger = 0):
self.scanner = scanner
self.x = x
self.y = y
self.danger = 'normal'
self.calcDanger(danger)
def drawMapNode(self, painter, realmap):
radiusm = self.scanner.range
radiusp = radiusm*realmap.pixpermet
factor = radiusp/200 # basis grootte gradiƫnten is 200 pixels.
icon = QtGui.QImage("graphics//BT-icon.png")
grad = QtGui.QImage(MapNode.dangertocolor[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,icon)
painter.drawText(self.x-15, self.y+20, str(self.scanner.sensorid) + '-' + str(self.scanner.name))
An object is made through our application class:
mapview = gfx.MapView(g_image)
mapview.show()
So the first question is. What are we doing wrong in the paintEvent method?
Secondly question
Is there a way to make the paintevent not be called at EVERY RANDOM THING that happens ? (like mouseovers, etc)?
I tried something like:
def paintEvent(self, event):
if(isinstance(event, QtGui.QPaintEvent)):
painter = QtGui.QPainter(self)
rect = self.contentsRect()
#teken achtergrond
self.realmap.drawRealMap(painter)
#teken nodes
for sensorid, mapNode in self.mapNodes.items():
mapNode.drawMapNode(painter, self.realmap)
else:
pass
This 'works' but is to general I guess.. It actually makes the error appear a lot faster then without the conditional.
When in your gfx.py you have:
self.repaint(self.map.contentsRect())
self.update(self.map.contentsRect())
Calling repaint and calling update one right after another is redundant. And if a paint event comes through that handler and you call repaint() there, you are asking for infinite recursion.
Take note of any Warnings or Notes in the documentation.
http://doc.qt.io/qt-4.8/qwidget.html#update
http://doc.qt.io/qt-4.8/qwidget.html#repaint
http://doc.qt.io/qt-4.8/qwidget.html#paintEvent
I don't see the cause for your other error right off, but it probably has to do with QPainter getting used when it shouldn't...
http://doc.qt.io/qt-4.8/qpainter.html#begin
http://doc.qt.io/qt-4.8/qpainter.html#details
Hope that helps.