Skip to content

GL Display Module

gl_display

This modules provides an OpenGL based viewer for graphs and meshes

Viewer

An OpenGL Viewer for Manifolds and Graphs. Having created an instance of this class, call display to show a mesh or a graph. The display function is flexible, allowing several types of interactive visualization. Each instance of this class corresponds to a single window, but you can have several GLManifoldViewer and hence also several windows showing different visualizations.

Source code in pygel3d/gl_display.py
class Viewer:
    """ An OpenGL Viewer for Manifolds and Graphs. Having created an instance of this
    class, call display to show a mesh or a graph. The display function is flexible,
    allowing several types of interactive visualization. Each instance of this
    class corresponds to a single window, but you can have several
    GLManifoldViewer and hence also several windows showing different
    visualizations. """
    def __init__(self):
        current_directory = getcwd()
        self.obj = lib_py_gel.GLManifoldViewer_new()
        chdir(current_directory) # Necessary because init_glfw changes cwd
    def __del__(self):
        lib_py_gel.GLManifoldViewer_delete(self.obj)
    def clone_controller(self, other: Self):
        """ Clone the controller from another GLManifoldViewer. This is useful if you
        want to display a mesh in a different window but keep the same view controller.
        """
        if isinstance(other, Viewer):
            lib_py_gel.GLManifoldViewer_clone_controller(self.obj, other.obj)
        else:
            raise TypeError("Argument must be an instance of Viewer")
    def display(self,
                m: Manifold,
                g: Graph=None,
                mode: str='w',
                smooth: bool=True,
                bg_col: tuple[float, float, float]=(0.3,0.3,0.3),
                data: ArrayLike|None=None,
                reset_view: bool=False,
                once: bool=False):
        """ Display a mesh

        Args:
        ---
        - m : the Manifold mesh or Graph we want to show.
        - g : the Graph we want to show. If you only want to show a graph, you
            can simply pass the graph as m, so the g argument is relevant only if
            you need to show both a Manifold _and_ a Graph.
        - mode : a single character that determines how the mesh is visualized:
            'w' - wireframe,
            'i' - isophote,
            'g' - glazed (try it and see),
            's' - scalar field,
            'l' - line field,
            'n' - normal.
            'x' - xray or ghost rendering. Useful to show Manifold on top of Graph
        - smooth : if True we use vertex normals. Otherwise, face normals.
        - bg_col : background color.
        - data : per vertex data for visualization. scalar or vector field.
        - reset_view : if False view is as left in the previous display call. If
            True, the view is reset to the default.
        - once : if True we immediately exit the event loop and return. However,
            the window stays and if the event loop is called from this or any
            other viewer, the window will still be responsive.

        Interactive controls:
        ---
        When a viewer window is displayed on the screen, you can naviagate with
        the mouse: Left mouse button rotates, right mouse button is used for
        zooming and (if shift is pressed) for panning. If you hold control, any
        mouse button will pick a point on the 3D model. Up to 19 of these points
        have unique colors.  If you pick an already placed annotation point it
        will be removed and can now be placed elsewhere. Hit space bar to clear
        the annotation points. Hitting ESC exits the event loop causing control
        to return to the script.
        """
        data_ct = np.array(data,dtype=ct.c_double).ctypes
        data_a = data_ct.data_as(ct.POINTER(ct.c_double))
        bg_col_ct = np.array(bg_col,dtype=ct.c_float).ctypes
        bg_col_a = bg_col_ct.data_as(ct.POINTER(ct.c_float*3))
        if isinstance(m, Graph):
            g = m
            m = None
        if isinstance(m,Manifold) and isinstance(g, Graph):
            lib_py_gel.GLManifoldViewer_display(self.obj, m.obj, g.obj, ct.c_char(mode.encode('ascii')),smooth,bg_col_a,data_a,reset_view,once)
        elif isinstance(m,Manifold):
            lib_py_gel.GLManifoldViewer_display(self.obj, m.obj, 0, ct.c_char(mode.encode('ascii')),smooth,bg_col_a,data_a,reset_view,once)
        elif isinstance(g,Graph):
            lib_py_gel.GLManifoldViewer_display(self.obj, 0, g.obj, ct.c_char(mode.encode('ascii')),smooth,bg_col_a,data_a,reset_view,once)

    def annotation_points(self) -> ArrayLike:
        """ Retrieve a vector of annotation points. This vector is not a copy,
        so any changes made to the points will be reflected in the viewer. """
        pos = ct.POINTER(ct.c_double)()
        n = lib_py_gel.GLManifoldViewer_get_annotation_points(self.obj, ct.byref(pos))
        if n == 0:
            return None
        return np.ctypeslib.as_array(pos,(n,3))
    def set_annotation_points(self, pts: ArrayLike):
        """ Set the annotation points to the given list of points. The points
        should be given as a flat list or array of size 3n where n is the
        number of points. """
        pts_ct = np.array(pts,dtype=ct.c_double).ctypes
        if pts_ct.size % 3 != 0:
            raise ValueError("Annotation points must be given as a flat array of size 3n")
        n = int(pts_ct.size // 3)
        pts_a = pts_ct.data_as(ct.POINTER(ct.c_double))
        lib_py_gel.GLManifoldViewer_set_annotation_points(self.obj, n, pts_a)
    @staticmethod
    def event_loop():
        """ Explicit call to the event loop. This function enters the event loop.
        Call it if you want to turn on interactivity in the currently displayed
        window."""
        lib_py_gel.GLManifoldViewer_event_loop(False)

clone_controller

clone_controller(other: Self)

Clone the controller from another GLManifoldViewer. This is useful if you want to display a mesh in a different window but keep the same view controller.

Source code in pygel3d/gl_display.py
def clone_controller(self, other: Self):
    """ Clone the controller from another GLManifoldViewer. This is useful if you
    want to display a mesh in a different window but keep the same view controller.
    """
    if isinstance(other, Viewer):
        lib_py_gel.GLManifoldViewer_clone_controller(self.obj, other.obj)
    else:
        raise TypeError("Argument must be an instance of Viewer")

display

display(m: Manifold, g: Graph = None, mode: str = 'w', smooth: bool = True, bg_col: tuple[float, float, float] = (0.3, 0.3, 0.3), data: ArrayLike | None = None, reset_view: bool = False, once: bool = False)

Display a mesh

Args:
  • m : the Manifold mesh or Graph we want to show.
  • g : the Graph we want to show. If you only want to show a graph, you can simply pass the graph as m, so the g argument is relevant only if you need to show both a Manifold and a Graph.
  • mode : a single character that determines how the mesh is visualized: 'w' - wireframe, 'i' - isophote, 'g' - glazed (try it and see), 's' - scalar field, 'l' - line field, 'n' - normal. 'x' - xray or ghost rendering. Useful to show Manifold on top of Graph
  • smooth : if True we use vertex normals. Otherwise, face normals.
  • bg_col : background color.
  • data : per vertex data for visualization. scalar or vector field.
  • reset_view : if False view is as left in the previous display call. If True, the view is reset to the default.
  • once : if True we immediately exit the event loop and return. However, the window stays and if the event loop is called from this or any other viewer, the window will still be responsive.
Interactive controls:

When a viewer window is displayed on the screen, you can naviagate with the mouse: Left mouse button rotates, right mouse button is used for zooming and (if shift is pressed) for panning. If you hold control, any mouse button will pick a point on the 3D model. Up to 19 of these points have unique colors. If you pick an already placed annotation point it will be removed and can now be placed elsewhere. Hit space bar to clear the annotation points. Hitting ESC exits the event loop causing control to return to the script.

Source code in pygel3d/gl_display.py
def display(self,
            m: Manifold,
            g: Graph=None,
            mode: str='w',
            smooth: bool=True,
            bg_col: tuple[float, float, float]=(0.3,0.3,0.3),
            data: ArrayLike|None=None,
            reset_view: bool=False,
            once: bool=False):
    """ Display a mesh

    Args:
    ---
    - m : the Manifold mesh or Graph we want to show.
    - g : the Graph we want to show. If you only want to show a graph, you
        can simply pass the graph as m, so the g argument is relevant only if
        you need to show both a Manifold _and_ a Graph.
    - mode : a single character that determines how the mesh is visualized:
        'w' - wireframe,
        'i' - isophote,
        'g' - glazed (try it and see),
        's' - scalar field,
        'l' - line field,
        'n' - normal.
        'x' - xray or ghost rendering. Useful to show Manifold on top of Graph
    - smooth : if True we use vertex normals. Otherwise, face normals.
    - bg_col : background color.
    - data : per vertex data for visualization. scalar or vector field.
    - reset_view : if False view is as left in the previous display call. If
        True, the view is reset to the default.
    - once : if True we immediately exit the event loop and return. However,
        the window stays and if the event loop is called from this or any
        other viewer, the window will still be responsive.

    Interactive controls:
    ---
    When a viewer window is displayed on the screen, you can naviagate with
    the mouse: Left mouse button rotates, right mouse button is used for
    zooming and (if shift is pressed) for panning. If you hold control, any
    mouse button will pick a point on the 3D model. Up to 19 of these points
    have unique colors.  If you pick an already placed annotation point it
    will be removed and can now be placed elsewhere. Hit space bar to clear
    the annotation points. Hitting ESC exits the event loop causing control
    to return to the script.
    """
    data_ct = np.array(data,dtype=ct.c_double).ctypes
    data_a = data_ct.data_as(ct.POINTER(ct.c_double))
    bg_col_ct = np.array(bg_col,dtype=ct.c_float).ctypes
    bg_col_a = bg_col_ct.data_as(ct.POINTER(ct.c_float*3))
    if isinstance(m, Graph):
        g = m
        m = None
    if isinstance(m,Manifold) and isinstance(g, Graph):
        lib_py_gel.GLManifoldViewer_display(self.obj, m.obj, g.obj, ct.c_char(mode.encode('ascii')),smooth,bg_col_a,data_a,reset_view,once)
    elif isinstance(m,Manifold):
        lib_py_gel.GLManifoldViewer_display(self.obj, m.obj, 0, ct.c_char(mode.encode('ascii')),smooth,bg_col_a,data_a,reset_view,once)
    elif isinstance(g,Graph):
        lib_py_gel.GLManifoldViewer_display(self.obj, 0, g.obj, ct.c_char(mode.encode('ascii')),smooth,bg_col_a,data_a,reset_view,once)

annotation_points

annotation_points() -> ArrayLike

Retrieve a vector of annotation points. This vector is not a copy, so any changes made to the points will be reflected in the viewer.

Source code in pygel3d/gl_display.py
def annotation_points(self) -> ArrayLike:
    """ Retrieve a vector of annotation points. This vector is not a copy,
    so any changes made to the points will be reflected in the viewer. """
    pos = ct.POINTER(ct.c_double)()
    n = lib_py_gel.GLManifoldViewer_get_annotation_points(self.obj, ct.byref(pos))
    if n == 0:
        return None
    return np.ctypeslib.as_array(pos,(n,3))

set_annotation_points

set_annotation_points(pts: ArrayLike)

Set the annotation points to the given list of points. The points should be given as a flat list or array of size 3n where n is the number of points.

Source code in pygel3d/gl_display.py
def set_annotation_points(self, pts: ArrayLike):
    """ Set the annotation points to the given list of points. The points
    should be given as a flat list or array of size 3n where n is the
    number of points. """
    pts_ct = np.array(pts,dtype=ct.c_double).ctypes
    if pts_ct.size % 3 != 0:
        raise ValueError("Annotation points must be given as a flat array of size 3n")
    n = int(pts_ct.size // 3)
    pts_a = pts_ct.data_as(ct.POINTER(ct.c_double))
    lib_py_gel.GLManifoldViewer_set_annotation_points(self.obj, n, pts_a)

event_loop staticmethod

event_loop()

Explicit call to the event loop. This function enters the event loop. Call it if you want to turn on interactivity in the currently displayed window.

Source code in pygel3d/gl_display.py
@staticmethod
def event_loop():
    """ Explicit call to the event loop. This function enters the event loop.
    Call it if you want to turn on interactivity in the currently displayed
    window."""
    lib_py_gel.GLManifoldViewer_event_loop(False)

The gl_display module provides OpenGL-based interactive 3D visualization for meshes and graphs.

Viewer Class

The Viewer class creates an OpenGL window for displaying and interacting with 3D geometry.

Creating a Viewer

import pygel3d.gl_display as gl
import pygel3d.hmesh as hmesh

# Create viewer
viewer = gl.Viewer()

# Load and display mesh
m = hmesh.load("model.obj")
viewer.display(m)

Display Methods

Main Display

  • viewer.display(mesh, mode, smooth, background, data) - Display a mesh or graph

Parameters

  • mesh: Manifold or Graph object to display
  • mode: Rendering mode (see below)
  • smooth: Enable smooth shading (default: True)
  • background: Background color as [r, g, b] (default: [0.3, 0.3, 0.3])
  • data: Optional attribute data for scalar/vector field visualization

Rendering Modes

The mode parameter controls how the geometry is rendered:

  • 'w': Wireframe - Show edges only
  • 'n': Normal - Flat shading with face normals
  • 'g': Glazed - Smooth shading (default)
  • 'i': Isophote - Isophote lines for curvature analysis
  • 'l': Line Field - Display vector field as lines (requires data)
  • 's': Scalar Field - Color-coded scalar values (requires data)
  • 'x': Ghost - Semi-transparent rendering

Interactive Controls

Mouse Controls

  • Left Mouse: Rotate camera
  • Right Mouse: Zoom in/out
  • Shift + Right Mouse: Pan camera

Keyboard Controls

  • ESC: Exit viewer
  • Space: Clear annotations

Annotation

The viewer supports interactive point annotation:

  • Ctrl + Click: Add/remove annotation point
  • Annotation points are displayed as colored spheres
  • Useful for marking features or measurements

Multiple Viewers

You can create multiple viewers to display different objects:

import pygel3d.gl_display as gl
import pygel3d.hmesh as hmesh

# Create two viewers
viewer1 = gl.Viewer()
viewer2 = gl.Viewer()

# Display different meshes
m1 = hmesh.load("model1.obj")
m2 = hmesh.load("model2.obj")

viewer1.display(m1, mode='g')
viewer2.display(m2, mode='w')

Example Usage

Basic Visualization

import pygel3d.gl_display as gl
import pygel3d.hmesh as hmesh

# Load mesh
m = hmesh.load("bunny.obj")

# Create viewer and display
viewer = gl.Viewer()
viewer.display(m, mode='g', smooth=True)

Custom Background

import pygel3d.gl_display as gl
import pygel3d.hmesh as hmesh

# Load mesh
m = hmesh.load("model.obj")

# Display with white background
viewer = gl.Viewer()
viewer.display(m, 
               mode='g', 
               smooth=True, 
               background=[1.0, 1.0, 1.0])

Scalar Field Visualization

import pygel3d.gl_display as gl
import pygel3d.hmesh as hmesh
import numpy as np

# Load mesh
m = hmesh.load("model.obj")

# Compute scalar field (e.g., mean curvature)
n_verts = m.no_vertices()
curvatures = []
for v in m.vertices():
    curv = hmesh.mean_curvature(m, v)
    curvatures.append(curv)

# Display with color-coded curvature
viewer = gl.Viewer()
viewer.display(m, mode='s', data=curvatures)

Vector Field Visualization

import pygel3d.gl_display as gl
import pygel3d.hmesh as hmesh

# Load mesh
m = hmesh.load("model.obj")

# Compute vector field (e.g., normals)
n_verts = m.no_vertices()
normals = []
for v in m.vertices():
    normal = hmesh.vertex_normal(m, v)
    normals.extend(normal)  # Flatten to [x0,y0,z0,x1,y1,z1,...]

# Display with line field
viewer = gl.Viewer()
viewer.display(m, mode='l', data=normals)

Wireframe Overlay

import pygel3d.gl_display as gl
import pygel3d.hmesh as hmesh

# Load mesh
m = hmesh.load("model.obj")

# Display as wireframe for topology inspection
viewer = gl.Viewer()
viewer.display(m, mode='w', background=[1.0, 1.0, 1.0])

Displaying Graphs

import pygel3d.gl_display as gl
import pygel3d.graph as graph

# Load or create graph
g = graph.load("skeleton.graph")

# Display graph
viewer = gl.Viewer()
viewer.display(g)

Tips

  • Performance: Large meshes may be slow to render; consider simplification
  • Smooth Shading: Better for organic shapes; use flat for architectural models
  • Background: Light backgrounds work well for presentations
  • Mode Selection: Try different modes to highlight different features
  • Annotations: Use for measurements or marking regions of interest