OGRE: Working with Python

| OGRE 14.5 Object-Oriented Graphics Rendering Engine

Loading...
Searching...
No Matches
Working with Python

Ogre features a Python component that automatically generates bindings from the C++ headers. This allows you to use Ogre in your Python code with an API that largely mirrors the C++ API, meaning you can often refer to the existing C++ documentation and tutorials.

Beyond this direct mapping, the bindings also support standard Python protocols. This enables a more "pythonic" workflow, especially when integrating with other libraries like NumPy.

Additionally, Ogre provides a high-level API called HighPy, designed for convenience and rapid development by abstracting away many of the engine's lower-level details.

# HighPy: A High-Level API

The HighPy module transforms Ogre into a lightweight Python renderer. As its name suggests, HighPy provides a high-level API that lets you get started quickly, even without extensive prior knowledge of the Ogre internals.

For instance, rendering a mesh is as simple as:

import Ogre.HighPy as ohi
# Add the path to your mesh files
ohi.user_resource_locations.add("your/meshes/path")
# Create a window and display the mesh
ohi.window_create("ohi_world", (800, 600))
ohi.mesh_show("ohi_world", "DamagedHelmet.glb", position=(0, 0, -3))
# Start the rendering loop
while ohi.window_draw("ohi_world") != 27: pass

Want to display a background image? It's just one line:

ohi.imshow("ohi_world", "image.png")

Integrating an ImGui-based user interface is also straightforward. Simply define a callback and enable ImGui for your window:

import Ogre.ImGui as ImGui
def ui_callback():
ImGui.ShowDemoWindow()
ohi.window_use_imgui("ohi_world", ui_callback)

For all details, please refer to the Python.HighPy module documentation.

# NumPy Interoperability

The Python bindings support the standard python protocols. In this tutorial, we will look how we can advantage of this to achieve numpy Interoperability in a pythonic way.

Note
  this tutorial can be run live in Google Colab here.

We start with a simple 3 channel python array representing a green gradient:

arr = np.zeros((256, 256, 3), dtype=np.uint8)
arr[:,:,1] = np.mgrid[0:256,0:256][1]

To be able to load it into Ogre we now have to convert it to Ogre.Image. The underlying C++ API takes a raw uchar*. However, the python bindings accept any object implementing the Buffer Protocol. This means we can pass the numpy array as is.

ogre_img = Ogre.Image()
ogre_img.loadDynamicImage(arr, 256, 256, Ogre.PF_BYTE_RGB)
Ogre.TextureManager.getSingleton().loadImage("gradient", "General", ogre_img)

Note that Ogre.Image is merely a view on the underlying array and no data is copied. While this is efficient, it also means that you have to ensure that the array does not get out of scope manually. Otherwise the application will crash due to accessing an invalid pointer.

For completeness we also create a small scene where we map the texture on a screen-centred rectangle.

mat = Ogre.MaterialManager.getSingleton().create("gradient_mat", "General")
rpass = mat.getTechniques()[0].getPasses()[0]
rpass.setLightingEnabled(False)
rpass.createTextureUnitState("gradient")
rect = scn_mgr.createScreenSpaceRect(True)
rect.setCorners(-0.5, 0.5, 0.5, -0.5) # in normalized screen space
rect.setMaterial(mat)
scn_mgr.getRootSceneNode().createChildSceneNode().attachObject(rect)

As the rectangle does not cover the full scene, we also set a background colour

gray = np.array([0.3, 0.3, 0.3])
vp.setBackgroundColour(gray)

Here, the standard python sequence protocol is used. Therefore, the data is copied.

Finally, we want read-back rendered image into an array. To avoid superficial copies of the data, we again allocate the memory with numpy:

mem = np.empty((win.getHeight(), win.getWidth(), 3), dtype=np.uint8)
pb = Ogre.PixelBox(win.getWidth(), win.getHeight(), 1, Ogre.PF_BYTE_RGB, mem)
win.copyContentsToMemory(pb, pb)

Note, that the convention of specifying width and height is swapped between Ogre and numpy.

Now we can store the image to disk using pyplot.

pyplot.imsave("screenshot.png", mem)

Note
  There is also Ogre::RenderTarget::writeContentsToFile if you do not need the pixel data in Python.

# Running Background Threads

By default, the Python bindings do not release the GIL (Global Interpreter Lock) during rendering. This design choice prioritizes performance by avoiding the overhead of constantly re-acquiring the GIL. However, it means that other Python threads will be blocked and unable to run concurrently with the rendering loop.

For example, the following code will not work as you might expect:

import threading
def printer():
for _ in range(15):
print("Thread")
threading.Thread(target=printer).start()
root.startRendering()

The "printer" Thread will be blocked until the rendering is finished.

To enable background threads to run, you can call root.allowPyThread(). This will instruct Ogre to release the GIL while waiting for vsync, allowing other Python threads to run during this idle time.