How to use panda3d in tkinter on Mac

I’m writing a game editor that has a tkinter GUI, and certain tabs have a 3D view powered by panda. I have this working fine on Windows and Linux, but I just tested on Mac for the first time, and panda is segfaulting when I call open_window. I understand that tkinter’s winfo_id isn’t meaningful on Mac, so I’m guessing that’s the problem. I’ve seen some examples online of using the pyobjc library to get a handle to the window itself, but I’m trying to embed panda in a specific widget, and I’m not sure how to go about getting a usable handle for that. What’s the ideal way to do this? For reference, here’s my code that’s working on other platforms. self is a ttk.Frame and self.base is the ShowBase object.

self.update()
width = self.winfo_width()
height = self.winfo_height()

props = WindowProperties()
props.setParentWindow(NativeWindowHandle.makeInt(self.winfo_id()))
props.setOrigin(0, 0)
props.setSize(width, height)
self._window = self.base.open_window(props)

The documentation suggests it this way.

https://docs.panda3d.org/1.10/python/reference/panda3d.core.GraphicsWindow#panda3d.core.GraphicsWindow.getWindowHandle

handle = base.win.getWindowHandle()

Stop, what does winfo_id() have to do with Panda3D? You should be able to create a window on a Mac, and get a handle accordingly.

from direct.showbase.ShowBase import ShowBase
from panda3d.core import WindowProperties
import tkinter

class AppTk(ShowBase):

    def __init__(self):
        ShowBase.__init__(self, windowType = 'none')
        base.startTk()

        self.frame = base.tkRoot
        self.frame.geometry("800x600")
        self.frame.title("Panda")

        props = WindowProperties()
        props.set_parent_window(self.frame.winfo_id())
        props.set_origin(0, 0)
        props.set_size(self.frame.winfo_width(), self.frame.winfo_height())

        base.make_default_pipe()
        base.open_default_window(props = props)

        self.frame.bind("<Configure>", self.resize)

        scene = loader.load_model("environment")
        scene.reparent_to(render)

    def resize(self, event):
        props = WindowProperties()
        props.set_origin(0, 0)
        props.set_size(self.frame.winfo_width(), self.frame.winfo_height())
        base.win.request_properties(props)

app = AppTk()
app.run()

I do it this way, but I don’t know if it works on Mac.

I just tried your example and it works on Linux (if I insert a call to base.tkRoot.update() before creating the WindowProperties, which is necessary to ensure the X window is ready) but it segfaults on Mac. This is on Python 3.12 with panda3d 1.10.14.

I think you need to make sure, as an experiment, that you can create a Tk window without Panda3D.

I can. My application doesn’t open any panda windows when it first starts, the user has to create or open a project first. The Tk window opens and works fine prior to that. Only once a project is opened and I start creating panda windows do I get a segfault on the first call to base.open_window.

For context, here’s the actual window code: https://github.com/descawed/galsdk/blob/master/galsdk/editor.py
Relevant portion:

class Editor(ShowBase):
    """The main editor window"""

    project: Optional[Project]

    def __init__(self):
        super().__init__(windowType='none')
        self.project = None
        self.project_open_complete = False

        cwd = Path.cwd()
        getModelPath().appendDirectory(cwd / 'assets')

        settings_path = cwd / 'editor.json'
        if settings_path.exists():
            with settings_path.open() as f:
                settings = json.load(f)
        else:
            settings = {}
        self.recent_projects = settings.get('recent', [])
        self.saved_geometry = settings.get('geometry')

        self.startTk()

Once a project is opened, I create the graphics pipe in open_project:

def open_project(self, project: Project):
        first_open = self.project is None
        self.project = project
        self.project_open_complete = False

        self.default_message.pack_forget()
        self.new_project_view.pack_forget()

        if self.pipe is None:
            self.makeDefaultPipe()

The tabs with 3D views use this Viewport widget: https://github.com/descawed/galsdk/blob/master/galsdk/ui/viewport.py
The segfault happens in the window property when the first tab is made active:

if self._window is None:
            self.update()
            width = self.winfo_width()
            height = self.winfo_height()

            props = WindowProperties()
            props.setParentWindow(NativeWindowHandle.makeInt(self.winfo_id()))
            props.setOrigin(0, 0)
            props.setSize(width, height)
            if self.current_cursor is not None:
                props.setCursorFilename(self.current_cursor.filename)
            # FIXME: this segfaults on Mac
            self._window = self.base.open_window(props)

I can’t remember exactly now, but I saw information somewhere that the problem may be with multithreading. Did you accidentally include multithreaded rendering on Mac in the configuration file?

threading-model Cull/Draw

I found it !

I haven’t provided any configuration file, so I should be using the default for everything. I see the docs say to use an empty string for single-threaded, so as a test, I added this to your example right before app = AppTk():

ConfigVariableBool('want-tk').setValue(True)
ConfigVariableString('threading-model').setValue('')

(I found another thread saying that “want-tk true” is necessary on Mac, so I included that too). Unfortunately, the result is the same.

You misunderstood me, I warned against enabling multithreading. I just assumed that you installed it on a Mac. I think you need to create a bug report on github.

from direct.showbase.ShowBase import ShowBase
from panda3d.core import WindowProperties
import tkinter

class AppTk(ShowBase):

    def __init__(self):
        ShowBase.__init__(self, windowType = 'none')
        base.startTk()

        self.frame = base.tkRoot
        self.frame.geometry("800x600")
        self.frame.title("Panda")
        self.frame.update()

        props = WindowProperties()
        props.set_parent_window(self.frame.winfo_id())
        props.set_origin(0, 0)
        props.set_size(self.frame.winfo_width(), self.frame.winfo_height())

        base.make_default_pipe()
        base.open_default_window(props = props)

        scene = loader.load_model("environment")
        scene.reparent_to(render)

app = AppTk()
app.run()

By posting this example, maybe Mac users will be able to test it.

rdb suggests in the post you found to set up the configuration this way, if it doesn’t work, then you need to create an error report.

from panda3d.core import WindowProperties, loadPrcFileData
loadPrcFileData("", "want-tk true")

from direct.showbase.ShowBase import ShowBase

class AppTk(ShowBase):

    def __init__(self):
        ShowBase.__init__(self, windowType = 'none')
        base.startTk()

        self.frame = base.tkRoot
        self.frame.geometry("800x600")
        self.frame.title("Panda")
        self.frame.update()

        props = WindowProperties()
        props.set_parent_window(self.frame.winfo_id())
        props.set_origin(0, 0)
        props.set_size(self.frame.winfo_width(), self.frame.winfo_height())

        base.make_default_pipe()
        base.open_default_window(props = props)

        scene = loader.load_model("environment")
        scene.reparent_to(render)

app = AppTk()
app.run()

Well I thought that setting threading-model to an empty string would ensure that multithreading was disabled, which is what you were suggesting, right? The docs for threading-model say: “This is the default threading model to use for new windows. Use empty string for single-threaded”

Regardless, I will go ahead with opening a bug report (I tried the loadPrcFileData form and that didn’t work either). Even if my window handle is invalid, a segfault is probably not the desired behavior. Thanks for your help!

As for the line in the configuration file, it is physically missing. At least in the Panda3D build for Windows.