Access Python Scene Graph in C++

Short version:
I’d like to access Panda3D objects (like the scene) that I initialized in Python via C++.
How to get an address and how to start a “C+±Module”?

Long version with background:
I am currently writing an Augmented Reality (AR) application. We chose Panda3D for scene management (big tanks for your engine!) using Python.
We wrote the rest of the application in C++.
When you define AR as margin the real world with a virtual one, you can interpret the Panda3D/Python part as the virtual world and the Rest/C++ part as the real world.
Now I need to merge both. This merging means I have to transfer the camera-position and some meshes.
The perfect solution would be accessing the scene-graph (managed in Python) directly in C++.

I already manipulated the scene-graph in a C+±only example. Now I am not sure how to get access to the Scene-Object (or something similar) in C++ that was initialized in Python.

I’ve written a small extension (with a great amount of help from rdb and CFSworks on Discord) that allows to execute Python script on the C++ side. It’s far from completion, but it can serve a good starting point.

Nice! I am glad that it is possible. Is the extension open source / are you able to share it? Do you have some links to the discussions?

We’ve discussed this in discord. I think it will be open source, I’ll upload it soon and give you a link.

Here it is: https://github.com/rasmaxim/panda-cpp-scripting
Use it if you figure out how to. There’s an example included.
It is a CMake project, but you can build it manually.

1 Like

@maxxim good thing, but I wonder if this approach slows down the speed of C++?

It doesn’t slow speed of C++ itself, but I think that running Panda3D Python code from C++ is slower than running Panda3D Python code the regular way. Maybe 5% slower, I didn’t make any tests. If you can make this thing running, you are welcome to try.

Thank you @maxxim for your prototype. It helps a lot. I am currently make it compile on my system (Ubuntu). There is a lot to do.
While this is a great start my dream would be to call a C++ module which has access to the stuff from python. As far as I can judge you run everything in C++ and start Python (as scripting).

Is there a possibility to get C+±Pointers of the objects defined in Python?
As far as I understand nearly every module has a C++ object.

A unrelated example which may clear my point. In OpenCL there is a C+±Wrapper around the C-objects. 99% of the time you are fine with the C++ wrapper object. But if not, every object has overloaded the ()-operator which returns the C-object.

Well that’s easier and even supported by Panda out of the box. You should create Python bindings to your C++ classes with the tool called interrogate. There should be some examples on the forum.

EDIT: on the second thought, maybe I’ve misread your post. Can you provide more detail, please?
Your program’s entry point is a Python script, correct? And you want to call your C++ module from there? Then yes, interrogate is the way to go.

Sorry, your example with OpenCL is not clear for me. I have little experience in C/C++ programming, in fact.

The C++ pointer value of a Python object can be obtained from Python by calling object.this, or in C/C++ code via the py_panda.h API (DtoolInstance_GetPointer or DtoolInstance_VOID_PTR without type checking). However, you usually don’t need to worry about this, as Interrogate will do the mapping for you.

Thank you all for your great help! As you surly observe: I am a bloody beginner with the framework.
Sorry @maxxim that I was not able to state my problem so that you can understand it. I give it a new try.

I created an example: https://github.com/tom-010/panda_python_cpp_call
To summarize the repo:

The python part (from the onboarding tutorial; walking panda)

# see onboarding tutorial on the website

class MyApp(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.addPandaVaiCpp() How?
        # Instead:
        self.addPanda()

    def addPanda(self):
        self.pandaActor = Actor("models/panda-model",
                                {"walk": "models/panda-walk4"})
        self.pandaActor.setScale(0.005, 0.005, 0.005)
        self.pandaActor.reparentTo(self.render)
        self.pandaActor.loop("walk")

    def addPandaViaCpp(self):
        pass # How? <---------------------------------

    def spinCameraTask(self, task):
       .... # see onboarding tutorial on website


app = MyApp()
app.run()

The same example (the walking panda) in C++

// ... see onboarding tutorial on the website

// I want to call this method from python
// The interface does not matter as long it works functionally
void addPanda(PandaFramework& framework, WindowFramework *window) {
  // Load our panda
  NodePath pandaActor = window->load_model(framework.get_models(), "models/panda-model");
  pandaActor.set_scale(0.005);
  pandaActor.reparent_to(window->get_render());

  // Load the walk animation
  window->load_model(pandaActor, "models/panda-walk4");
  window->loop_animations(0); // bind models and animations
                              //set animations to loop

}

// ... see onboarding tutorial on the website
int main(int argc, char *argv[]) {
  // ... see onboarding tutorial on the website
  addPanda(framework, window);
  // ... see onboarding tutorial on the website
  framework.main_loop();
  // ... see onboarding tutorial on the website
}

So the concrete question: How to implement the python method

def addPandaViaCpp(self):
    pass # How?

that it calls this C++ function?

void addPanda(PandaFramework& framework, WindowFramework *window) {
  NodePath pandaActor = window->load_model(framework.get_models(), "models/panda-model");
  pandaActor.set_scale(0.005);
  pandaActor.reparent_to(window->get_render());
  window->load_model(pandaActor, "models/panda-walk4");
  window->loop_animations(0); 
}

The signature in the last listing describes my problem. How to get a reference/pointer for (PandaFramework& framework, WindowFramework *window) and how to call the function?

OK, now I understand what you are trying to achieve.

Using this particular signature you won’t be able to call addPanda C++ method of your library from Python script.

Why? Becasue to set up the window, model loader, keyboard and mouse devices, etc Python version of Panda uses a different API, a pure-python implemented class ShowBase, as you can see.

C++ API, on the other hand, needs to use PandaFramework and WindowFramework classes, that are not exposed to Python. You can’t pass the pointer of PandaFramework and WindowFramework from Python to C++ because these objects are not created by ShowBase or any other Python code. Even if they were, there would be little use of them, unless you want to write your ShowBase equivalent from scratch.

You shouldn’t use PandaFramework and WindowFramework in this case at all, these classes serve when your program’s entry point is C++ program. You should modify your C++ method signature to accept classes that have both C++ and Python interfaces and are already instanciated in Python script.

E.g. your C++ method can accept the scene root (base.render), or any other NodePath to which you want to reparent a loaded model. NodePaths and Nodes are C++ objects that have their API exposed to Python, so you can pass them back and forth freely.

Then you need to expose your C++ class to Python using interrogate - i.e. create a .pyd module with your compiled code. I think there are some examples on the site.

Sorry for mistakes, it’s already late. I hope that my post helps at least a bit.

This helps a lot, @maxxim !
I am struggling with Interrogate since yesterday when @rdb mentioned it.

A question remains. While I have at least an idea, how to “pass” a NodePath to my C++ Function render, I have no idea how to load models in C++ without the window given:

You shouldn’t use PandaFramework and WindowFramework in this case at all, these classes serve when your program’s entry point is C++ program. You should modify your C++ method signature to accept classes that have both C++ and Python interfaces and are already instanciated in Python script.

void addPanda(NodePath render) {
  // HOW? NodePath pandaActor = window->load_model(framework.get_models(), "models/panda-model");
  pandaActor.reparent_to(render); // OK
  // HOW? window->load_model(pandaActor, "models/panda-walk4");
  // HOW? window->loop_animations(0); 
}

There are 2 options:

  1. You should look into the source code to see how the WindowFramework does it and do it yourself.
    If I am not mistaken, it creates a Loader object and then loads a model with loadSync method. Loader class is available in Python as well, so you can either instanciate it in C++ code or pass it to your addPanda method from Python script.
  2. You can create WindowFramework and PandaFramework instances in your C++ method each time you call it. That’s easier, but would be a waste of resources.

I “successfully” implemented option two:

// This class is required as the constructor of WindowFramework is protected
class WindowFrameworkPublic : public WindowFramework {
public: 
  WindowFrameworkPublic(PandaFramework* framework): WindowFramework(framework) {
  }
};

void addPanda(NodePath render) {
  PandaFramework framework;
  WindowFrameworkPublic window(&framework);
  NodePath pandaActor = window.load_model(framework.get_models(), "models/panda-model");
  pandaActor.reparent_to(render); // OK
  pandaActor.set_scale(0.0025 , 0.0025, 0.0025); // I need another scale. Strange?
  window.load_model(pandaActor, "models/panda-walk4");
  window.loop_animations(0); 
}

I don’t think that this wastes much resources. The C++ part is running concurrently with Python and I do have to instantiate the resources only once (I would extract them from the method above).

Are there any other downsides of this approach?

I try to solve the passing of the NodePath to the function to come to a working prototype

You just need to compile yout WindowFrameworkPublic class to a python extension.

Are there any other downsides of this approach?

I think, this won’t work, for example:

  window.load_model(pandaActor, "models/panda-walk4");
  window.loop_animations(0); 

Why? Because your Python script doesn’t have access to the WindowFramework class. The main game loop on the Python side is implemented in ShowBase, and actor animations are handled by Actor class, which is a pure Python class.

Oh, you are right. The panda stopped moving.

You just need to compile yout WindowFrameworkPublic class to a python extension.

Working on it :slight_smile: