Here’s how I do this in my own project:
from direct.showbase.ShowBase import ShowBase, DirectObject
from panda3d.core import *
class WorldAxesTripod:
def __init__(self, showbase):
self.win = showbase.win
self.dr_pixel_size = 68 # size of display region in pixels
dr = showbase.win.make_display_region(0., 1., 0., 1.)
dr.sort = 2
lens = OrthographicLens()
lens.film_size = .235
self._cam_target = cam_target = NodePath("tripod_cam_target")
cam_target.set_compass(showbase.cam)
cam_node = Camera("tripod_cam")
camera = cam_target.attach_new_node(cam_node)
camera.node().set_lens(lens)
dr.camera = camera
dr.set_clear_color_active(False)
dr.set_clear_depth_active(True)
self._display_region = dr
self._root = camera.attach_new_node("world_axes")
self._root.set_y(10.)
self._axis_tripod = self.__create_axis_tripod()
node = self._root.node()
node.set_bounds(OmniBoundingVolume())
node.final = True
self.listener = DirectObject.DirectObject()
self.listener.accept("aspectRatioChanged", self.__update_region_size)
def __create_axis_tripod(self):
vertex_format = GeomVertexFormat.get_v3c4()
vertex_data = GeomVertexData("axis_tripod_data", vertex_format, Geom.UH_static)
pos_writer = GeomVertexWriter(vertex_data, "vertex")
col_writer = GeomVertexWriter(vertex_data, "color")
tripod = GeomLines(Geom.UH_static)
for i in range(3):
v_pos = VBase3()
pos_writer.add_data3(v_pos)
v_pos[i] = .1
pos_writer.add_data3(v_pos)
color = VBase4(0., 0., 0., 1.)
color[i] = 1.
col_writer.add_data4(color)
col_writer.add_data4(color)
tripod.add_vertices(i * 2, i * 2 + 1)
tripod_geom = Geom(vertex_data)
tripod_geom.add_primitive(tripod)
tripod_node = GeomNode("axis_tripod")
tripod_node.add_geom(tripod_geom)
axis_tripod = self._root.attach_new_node(tripod_node)
axis_tripod.set_compass()
return axis_tripod
def __update_region_size(self):
win_w, win_h = self.win.properties.size
aspect_ratio = win_w / win_h
size_h = self.dr_pixel_size / win_w
size_v = size_h * aspect_ratio
self._display_region.dimensions = (0., size_h, 0., size_v)
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
terrain = loader.loadModel('../samples/Roaming-Ralph/models/world')
terrain.reparentTo(render)
self._tripod = WorldAxesTripod(self)
app = MyApp()
app.run()
Note that two compass effects are needed to give the axis tripod the correct orientation. Also, this method does not require disabling depth-testing, so you can use whatever 3D model you wish for the tripod (otherwise, visually overlapping triangles in the geometry might not be rendered correctly).
As a bonus, the display region frame is updated each time the window size is changed, so the tripod will retain its apparent size, regardless of window size.
Although it’s probably not that hard to change the location of the display region (it’s on the left in my code), I’ll leave that as an exercise for the reader .