Inkblot
Posts: 15
Joined: Wed Oct 24, 2018 5:13 pm
Location: UK

Pi3D - Embedding Display in Tkinter Window

Tue Jun 23, 2020 9:04 pm

I'm trying to place a Pi3d Display in a tkinter Frame

Looking over the source code, I can see that the TkWin class inherits from Tk so I could (in theory) use:

Code: Select all

from pi3D import Display
from tkinter import Tk

def config(parent, win):
    win.overrideredirect(True)
    win.transient(parent)

def run():
    disp.loop_running()
    # animation stuff here
    root.after(100, run)

root = Tk()
disp = Display.create(x=0, y=0, w=520, h=372, tk=True)
disp.resize(562, 411, 520, 372)
config(root, disp.tkwin)
run()
root.mainloop()
   
This should remove the titlebar from the pi3D window and group it with the tkinter window, essentially making it behave like a tkinter Toplevel widget, giving the impression that it is embedded in the root window.

However, this class is only used on Android and Raspberry pi (I'm on Ubuntu) and the class used on other systems is one that does not inherit from Tk and hence this solution won't work for it.

I've taken a look at the use_pygame option but it is stated in the source that it does not work for Linux and also means the display must be fullscreen anyways - a restriction I'd prefer not to have.

Is there any other way to achieve the intended solution? I have taken a look at the PyOpenGL library but I (obviously) prefer the abstractions that pi3D offer.

EDIT:
I have come across the display_config option and found the following options:

Code: Select all

DISPLAY_CONFIG_DEFAULT = 0
DISPLAY_CONFIG_NO_RESIZE = 1
DISPLAY_CONFIG_NO_FRAME = 2
DISPLAY_CONFIG_FULLSCREEN = 4
DISPLAY_CONFIG_MAXIMIZED = 8
DISPLAY_CONFIG_HIDE_CURSOR = 16
However, using any of them seems to have no effect.

Inkblot
Posts: 15
Joined: Wed Oct 24, 2018 5:13 pm
Location: UK

Re: Pi3D - Embedding Display in Tkinter Window

Fri Jun 26, 2020 6:28 pm

Just an update on some issues I've run into

I'm trying to read mouse inputs from the Pi3D window but have run into some difficulties.

Assuming a mouse attribute inside in a class:

Code: Select all

def loop(self):
        mx, my = self.mouse.position()
        self.mouse_clicked(self.mouse.button_status(), mx, my)
def mouse_clicked(self, btn, mx, my):
        print(btn)  
        
My system's (Ubuntu 18.04) touchpad has no distinct buttons, i.e. bottom left is lmb, bottom right is rmb and I think because of this, no matter where I click the touchpad it is always interpreted as the left mouse button.

To fix this, I tried to use the InputEvents class but it doesn't seem to work, simply trying to instantiate it, like so:

Code: Select all

self.input_events = InputEvents()
Freezes my laptop to the point where I have to hold the power button until it shuts down completely.

Is there any way I can read mouse inputs with Pi3D on Linux?

User avatar
paddyg
Posts: 2529
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Pi3D - Embedding Display in Tkinter Window

Tue Jun 30, 2020 10:16 pm

@Inkblot, sorry not to respond before. I do all the development of pi3d on ubuntu 18.04 so it ought to work ok. Embedding the display surface that pi3d (OpenGL) writes to in another GUI toolkit is a bit messy - I've done a couple of examples in pi3d_demos using GTK and Qt (I think the Qt one was more satisfactory but can't remember why) You have to let the toolkit app do the event loop and make it call the Display.loop_running() like in this https://github.com/pi3d/pi3d_demos/blob ... be.py#L100. I've just tried running that demo PyQtCube.py on this laptop and there are a couple of issues: 1. I get an error message ``Failed to load module "canberra-gtk-module"`` which seems odd 2. The drawing surface window that pi3d uses and from where the image is copied isn't being moved to be behind the the x window used by Qt (or just hidden completely). I think the methods for doing that might have been fixed after the demo was made so I will review the code and make it nicer.

On the mouse button question I will also have a look at what my laptop does and if there is some info available from the xserver that pi3d could pass on.

Paddy
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Inkblot
Posts: 15
Joined: Wed Oct 24, 2018 5:13 pm
Location: UK

Re: Pi3D - Embedding Display in Tkinter Window

Wed Jul 01, 2020 2:11 pm

Hi @paddyg I think you're onto something here with those demos

In the demos, the 'embedding' is just taking a screenshot and pasting it into the GUI toolkit so I tried the same with tkinter:

Code: Select all

from tkinter import Tk, Canvas

import pi3d
from PIL import ImageTk, Image


class TkWin(Tk):
    def __init__(self, title):
        super().__init__(className=title, baseName=title)
        self.DISPLAY = pi3d.Display.create(w=500, h=500, layer=-128)
        self.cube = pi3d.Cuboid(z=2)
        self.display_canvas = None
        self.canvas_image = None
        self.initUI()
        self.pi3d_loop()

    def initUI(self):
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        self.display_canvas = Canvas(self, bg='blue')
        self.display_canvas.grid(row=0, column=0, sticky='nesw')
        self.canvas_image = ImageTk.PhotoImage(Image.open('blank.jpg'))
        self.display_canvas.create_image(0, 0, anchor='nw', image=self.canvas_image, tags='image')

    def pi3d_loop(self):
        self.DISPLAY.loop_running()
        self.cube.draw()
        self.cube.rotateIncY(360 / 86400 * 250)
        self.swap_image(pi3d.screenshot())
        self.after(10, self.pi3d_loop)

    def swap_image(self, new_image):
        self.canvas_image = ImageTk.PhotoImage(Image.fromarray(new_image))
        self.display_canvas.itemconfig('image', image=self.canvas_image)


win = TkWin('test')
win.mainloop()
This works perfectly fine except for the actual pi3d window still being there. Performance isn't the greatest but the difference is only noticeable to me because I've run (too) many pi3d simulations these past few days. Blindly slapping the canvas and swap_image function into my actual application means It now uses 20% of my CPU (previously 9-12%), maybe a little optimisation is needed on my part...

Now that I can show the content in a tkinter canvas I will have a go at trying to read mouse inputs on the canvas instead, rather than the pi3d display and post my findings.

Thanks.

User avatar
paddyg
Posts: 2529
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Pi3D - Embedding Display in Tkinter Window

Wed Jul 01, 2020 3:08 pm

@Inkblot, sounds hopeful, and I would be keen to add the code, once it's fixed, so there's a tk alternative to qt and gtk in pi3d_demos.

I will revisit the issue of hiding the screen as well possibly more efficient ways of doing this later tonight if I get a chance.

Paddy
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Inkblot
Posts: 15
Joined: Wed Oct 24, 2018 5:13 pm
Location: UK

Re: Pi3D - Embedding Display in Tkinter Window

Wed Jul 01, 2020 3:24 pm

@paddyg Great!

So I've had a little play around and I can confirm the solution does work. I just need to setup the scene and then do all input reading via tkinter and then run the appropriate functions to alter the scene.

Only issue is that the inputs are a little slow but for now it's perfectly fine - I'll see what's causing the delays later.

All that's left now is hiding that display for good!

User avatar
paddyg
Posts: 2529
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Pi3D - Embedding Display in Tkinter Window

Wed Jul 01, 2020 7:13 pm

Hiding the pi3d window is easy enough now. Use the Display.resize() method to move it off to one side (or off the bottom). I also created the blank canvas from the code rather than having an image the right size.

Code: Select all

from tkinter import Tk, Canvas
import numpy as np
import demo
import pi3d
from PIL import ImageTk, Image

(W, H) = (500, 500)

class TkWin(Tk):
    def __init__(self, title):
        super().__init__(className=title, baseName=title)
        self.display = pi3d.Display.create(w=W, h=H)
        self.cube = pi3d.Cuboid(z=2)
        self.display_canvas = None
        self.canvas_image = None
        self.initUI()
        self.display.resize(x=0, y=self.display.max_height, w=W, h=H) ##<<<<<<<<<<<<
        self.pi3d_loop()

    def initUI(self): # I don't really understand the virtue of splitting __init__ into two functions..
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        self.display_canvas = Canvas(self, bg='blue', width=W, height=H) ##<<<<<<<<<<<<<<<<<<
        self.display_canvas.grid(row=0, column=0, sticky='nesw')
        self.canvas_image = ImageTk.PhotoImage(Image.fromarray(np.zeros((W,H,3), dtype=np.uint8))) ##<<<<<<<<<<<<<<<
        self.display_canvas.create_image(0, 0, anchor='nw', image=self.canvas_image, tags='image')
...
My poor laptop runs at 50% of one CPU with this and up to 65% if I increase the pi3d window to 750x750 so saving and copying the pixels in this way is quite an overhead. I will have another look at getting hold of a drawing surface from tk and rendering directly into that - so long ago now I can't remember what the problems were exactly.

Paddy

PS pyOpenGL seems to use GLX so maybe I should look at that. I did a bit of work with that to allow transparent window backgrounds not long ago so I might have some code in there that I can tweak!
Last edited by paddyg on Wed Jul 01, 2020 8:46 pm, edited 1 time in total.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Inkblot
Posts: 15
Joined: Wed Oct 24, 2018 5:13 pm
Location: UK

Re: Pi3D - Embedding Display in Tkinter Window

Wed Jul 01, 2020 8:46 pm

Thanks again paddy, I'll be here waiting for a response ;) in the meantime, I'll see if I can optimise my end a bit to reduce cpu usage

Return to “Graphics programming”