Executing code post `app.run()`

Consider the simplest possible panda3d app:


from direct.showbase.ShowBase import ShowBase

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)


app = MyApp()
app.run()
print('hello')

How can I get code after app.run() to execute? At present, the only way I can manage that is to press ctrl+c from within python. Ideally, I would like to map a keyboard press within the app that allows print('hello') to be ran without the app window closing.

To explain a bit, it’s not executing, I believe, because the call to “run” is starting Panda’s internal game-loop, which doesn’t end until it’s stopped. As a result (and as generally used; there are exceptions, I think), code that’s placed after “run” will only execute after the game is closed.

As to getting the code in question to be run, one way is to include it within your “MyApp” class, or within some code that’s executed by that class. Something like this, for one example:

from direct.showbase.ShowBase import ShowBase

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        self.mew()
     
    def mew(self):
        print ("Hello")

app = MyApp()
app.run()

(Naturally, you could include the print-statement directly within the “__init__” method. However, key-press events generally call methods, hence the placement here within a separate method.)

Thanks for the reply @Thaumaturge.

As to getting the code in question to be run, one way is to include it within your “MyApp” class, or within some code that’s executed by that class

That doesn’t really suit my design needs. I should have explained better. I have a reason for wanting to use panda3d as a visualization tool ran in the midst of a script. Similar to how one may want to generate a plot in the middle of a script with matplotlib, look at it via a pop up window, and then exit the window to continue the script.

Here is a more complex example that better illustrates what I’m working on.

In codebase.py:

#! /usr/bin/env python

from math import pi, sin, cos

from direct.showbase.ShowBase import ShowBase
from direct.task import Task


class Visualizer(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        self.scene = self.loader.loadModel("models/environment")
        self.scene.reparentTo(self.render)
        self.scene.setScale(0.25, 0.25, 0.25)
        self.scene.setPos(-8, 42, 0)

        self.taskMgr.add(self.spinCameraTask, "SpinCameraTask")
        self.speed = None


    def spinCameraTask(self, task):
        angleDegrees = task.time * self.speed
        angleRadians = angleDegrees * (pi / 180.0)
        self.camera.setPos(20 * sin(angleRadians), -20 * cos(angleRadians), 3)
        self.camera.setHpr(angleDegrees, 0, 0)
        return Task.cont


    def configure(self, speed):
        self.speed = speed


    def start(self):
        self.run()

In script.py:

#! /usr/bin/env python

import random
from codebase import Visualizer

interface = Visualizer()

def big_computation():
    return random.randint(0, 50)

for i in range(10):
    output = big_computation()
    interface.configure(output)
    interface.start()

If you run python script.py, the panda3d window pops up upon the line of code interactive = Visualizer(). Great. Then, after big_computation(), the visualization is properly configured and started. In the Python interpreter, every time I press ctrl+c, the loop continues, the visualizer is reconfigured, and its all good. The problem is that I do not want to continue the loop via ctrl+c within the interpreter. Instead, I want to continue the loop via a key press within the panda3d app.

Do you want to pause your script while the Panda3D window is open, or do you want the Panda3D main loop to continue in the background while your script continues to run?

If the former, you can override the finalizeExit function in your ShowBase subclass to do something other than sys.exit, probably simply make it a no-op, I think then run() will simply return when you close the window.

If you want to trigger run() to return, for example on a keypress, then all you need to do is stop the task manager:

    def __init__(self):
        ...
        self.accept('s', self.stop)

    def start(self):
        self.run() # really just calls self.taskMgr.run()

    def stop(self):
        self.taskMgr.stop()

However, when the main loop is not running, you cannot catch key events in the Panda3D window, you would need to call run() again or periodically poll taskMgr.step() to process the key inputs.

If you want to be able to run other Python code while the Panda3D main loop is still running, your options are:

  1. Run the other code via the Panda3D task system.
  2. Don’t call run(), instead call self.taskMgr.step() yourself periodically as scheduled by some external main loop, allowing you to put your other code between those step calls.
  3. Use a separate thread or process so that the Panda3D main loop and your other code can run concurrently.
3 Likes

This is great @rdb, thank you.

Do you want to pause your script while the Panda3D window is open, or do you want the Panda3D main loop to continue in the background while your script continues to run?

Pausing the script.

If the former, you can override the finalizeExit function in your ShowBase subclass to do something other than sys.exit , probably simply make it a no-op, I think then run() will simply return when you close the window.

Good idea. It works, but the window remains closed for all subsequent run() calls. Is there any method I could call to reinitialize the window?

EDIT: I was wrong. Overwriting finalizeExit doesn’t lead to run() returning. Instead, it hangs somewhere.

If you want to trigger run() to return, for example on a keypress, then all you need to do is stop the task manager.

This also works like a charm. Similar to my above question, is there a method I could call in stop(self) that hides or destroys the window, and then a method in start(self) that shows or reinitializes the window?

I think this should do what you need:

    def start(self):
        if not self.win:
            self.openMainWindow()
        self.taskMgr.run()

    def stop(self):
        if self.win:
            self.closeWindow(self.win)
        self.taskMgr.stop()

    def finalizeExit(self):
        self.stop()

I’m grinning ear to ear! This does exactly what I want. Thanks as always.


Anyone from the future, here is the working example that implements @rdb’s suggestions. The main script continues upon closing the window, or via a keypress, which triggers the panda window to disappear. The next time the ShowBase subclass is called into action with start, the window is reopened:

codebase.py:

#! /usr/bin/env python

from math import pi, sin, cos

from direct.showbase.ShowBase import ShowBase
from direct.task import Task


class Visualizer(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        self.scene = self.loader.loadModel("models/environment")
        self.scene.reparentTo(self.render)
        self.scene.setScale(0.25, 0.25, 0.25)
        self.scene.setPos(-8, 42, 0)

        self.taskMgr.add(self.spinCameraTask, "SpinCameraTask")
        self.speed = None

        self.accept('s', self.stop)


    def spinCameraTask(self, task):
        angleDegrees = task.time * self.speed
        angleRadians = angleDegrees * (pi / 180.0)
        self.camera.setPos(20 * sin(angleRadians), -20 * cos(angleRadians), 3)
        self.camera.setHpr(angleDegrees, 0, 0)
        return Task.cont


    def configure(self, speed):
        self.speed = speed


    def start(self):
        if not self.win:
            self.openMainWindow()
        self.taskMgr.run()


    def stop(self):
        if self.win:
            self.closeWindow(self.win)
        self.taskMgr.stop()


    def finalizeExit(self):
        self.stop()

script.py:

#! /usr/bin/env python

import random
from codebase import Visualizer

interface = Visualizer()

def big_computation():
    import time; time.sleep(2)
    return random.randint(0, 50)

for i in range(10):
    output = big_computation()
    interface.configure(output)
    interface.start()
2 Likes