Visualize Scene Graph Python

When the project gets huge I often find it difficult to keep track of all the nodes that are present in a scene graph. Here is a simple code to plot scene graph:

import panda3d.core as p3d
import pydot
import cv2
import numpy as np

def get_label(node: p3d.NodePath):
    """
    Get relevant values from a node path
    
    :param node: 
    :return: 
    """
    x, y, z = node.getPos()
    h, p, r = node.getHpr()
    label_dict = {'x': x, 'y': y, 'z': z, 'h': h, 'p': p, 'r': r}
    label = ''
    for key, value in label_dict.items():
        if value == 0:
            continue
        label += '{}={:.2f}\n'.format(key, value)
    return label


def draw_scene_graph(rootnode: p3d.NodePath, graph: pydot.Dot = None, node_idx=0):
    """
    A simple visualization function for the scene graph
    
    :param rootnode: The reference node whose children you wish to visualize
    :param graph: pydot graph, leave to None to automatically create a new graph
    :param node_idx: current node index, do not change, used for recursion
    :return: image of the graph 
    """
    if graph is None:
        graph = pydot.Dot('my_graph', graph_type='graph', bgcolor='white')
    label = rootnode.name
    info_label = get_label(rootnode)
    if info_label != '':
        label += '\n' + info_label

    graph.add_node(pydot.Node(str(rootnode), shape='circle', label=label))
    if len(rootnode.ancestors) > 1:
        graph.add_edge(pydot.Edge(str(rootnode.ancestors[1]), str(rootnode), color='blue'))

    for childnode in rootnode.children:
        draw_scene_graph(childnode, graph, node_idx + 1)
    if node_idx == 0:
        im_buf = graph.create_png()
        im_arr = np.frombuffer(im_buf, 'uint8')
        im_cv = cv2.imdecode(im_arr, cv2.IMREAD_ANYCOLOR)[..., ::-1]
        return im_cv

Sample Usage:

import panda3d.core as p3d
import matplotlib.pyplot as plt
root  = p3d.NodePath('root')

child1 = [root.attach_new_node(f'child{i}') for i in range(10)]
for child in child1:
    child.setPos(*np.random.rand(3))

child2 = [child1[2].attach_new_node(f'child{i}') for i in range(5)]
for child in child2:
    child.setHpr(*np.random.rand(3))
plt.figure(figsize=(20,20))            
plt.imshow(draw_scene_graph(root))
plt.show()

Nodes from my project:

Note:
To view live changes in your graph while panda app is running call the draw_scene_graph function from taskMgr. Show the returned image in opencv or any other library of your choice so you will be able to view new nodes and changing values of x,y,z,h,p, and r in the graph in real time.

8 Likes

That is pretty cool! I can see it being a pretty useful debugging/performance-tuning tool, for one! :slight_smile:

2 Likes