Utils API

The utils module provides utility functionality for PicoGL, including data loading, texture management, and helper functions.

Core Classes

ObjectLoader

class picogl.utils.loader.object.ObjectLoader(path: str)[source]

Bases: object

Object Loader Class

__init__(path: str)[source]
log_properties()[source]

log object properties

to_array_style() ObjectData[source]

Convert to array-style where each vertex attribute is stored separately

to_single_index_style() ObjectData[source]

Convert to single-index style where each unique vertex attribute combination is stored once

The ObjectLoader class provides functionality for loading 3D object files (OBJ format).

Example:

from picogl.utils.loader.object import ObjectLoader

# Load OBJ file
loader = ObjectLoader("model.obj")
data = loader.to_array_style()

# Create mesh data
from picogl.renderer import MeshData
mesh_data = MeshData.from_raw(
    vertices=data.vertices,
    normals=data.normals,
    colors=data.colors
)

TextureLoader

class picogl.utils.loader.texture.TextureLoader(file_name: str, mode: str = 'RGB')[source]

Bases: object

Loads a 2D texture from a DDS file or a standard image file using PIL. Automatically creates an OpenGL texture ID.

__init__(file_name: str, mode: str = 'RGB') None[source]
load_dds(file_name: str) None[source]

Load a DDS texture from file. Supports DXT1, DXT3, DXT5 compressed textures. Falls back to PIL loading if compressed texture loading fails.

load_by_pil(file_name: str, mode: str) None[source]

Load a standard image using PIL and upload as OpenGL texture.

delete() None[source]

Deletes the OpenGL texture to free GPU memory.

The TextureLoader class provides functionality for loading and managing textures.

Example:

from picogl.utils.loader.texture import TextureLoader

# Load texture
loader = TextureLoader("texture.png")
texture = loader.load()

# Use texture
texture.bind()
# Draw textured geometry

Helper Functions

GL Initialization

picogl.utils.gl_init.execute_gl_tasks(task_list: list[tuple[str, Callable]])[source]

Execute a sequence of OpenGL-related tasks.

Each task is a tuple (message, func): - message (str or None): If a string, it is logged before running the task.

If None, no log message is emitted for that step.

  • func (callable): The function to execute.

Parameters:

task_list (list[tuple[str | None, callable]]) – A list of (message, callable) tuples describing the tasks to run.

Raises:
  • TypeError – If task_list is not a list or any element is not a 2-tuple.

  • Exception – Logs and re-raises any exception thrown by a task.

Executes a list of OpenGL initialization tasks.

Example:

from picogl.utils.gl_init import execute_gl_tasks, init_gl_list

# Execute initialization tasks
execute_gl_tasks(init_gl_list)
picogl.utils.gl_init.init_gl_list()[source]

Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.

Returns a list of OpenGL initialization tasks.

Example:

from picogl.utils.gl_init import init_gl_list

# Get initialization tasks
tasks = init_gl_list
execute_gl_tasks(tasks)
picogl.utils.gl_init.paint_gl_list()[source]

Built-in mutable sequence.

If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.

Returns a list of OpenGL painting tasks.

Example:

from picogl.utils.gl_init import paint_gl_list

# Get painting tasks
tasks = paint_gl_list
execute_gl_tasks(tasks)

Texture Utilities

picogl.utils.texture.bind_texture_array(texture_id: int)[source]
Parameters:

texture_id

Returns:

None

Binds a texture array to the specified texture unit.

Example:

from picogl.utils.texture import bind_texture_array

# Bind texture to unit 0
bind_texture_array(texture_id, 0)

Normal Generation

picogl.utils.normal.compute_vertex_normals(vertices, indices)[source]

Generates surface normals for a mesh.

Example:

from picogl.utils.normal import compute_vertex_normals

# Generate normals
normals = compute_vertex_normals(vertices, faces)

Reshape Utilities

picogl.utils.reshape.float32_row(array_like: Any) ndarray[source]

Reshape input to a single row and convert to np.float32.

Parameters:

array_like – Any array-like object (list, tuple, np.ndarray).

Returns:

A NumPy array of shape (1, N) with dtype np.float32.

Reshapes input to a single row and converts to np.float32.

Example:

from picogl.utils.reshape import float32_row

# Reshape data
data = float32_row([1, 2, 3, 4, 5])

Data Loading

OBJ File Loading

Basic Loading:

from picogl.utils.loader.object import ObjectLoader

# Load OBJ file
loader = ObjectLoader("model.obj")
data = loader.to_array_style()

# Access data
vertices = data.vertices
normals = data.normals
colors = data.colors
uvs = data.uvs

Advanced Loading:

# Load with options
loader = ObjectLoader("model.obj")
loader.set_options(
    normalize=True,
    compute_normals=True,
    generate_colors=True
)

data = loader.to_array_style()

Error Handling:

try:
    loader = ObjectLoader("model.obj")
    data = loader.to_array_style()
except FileNotFoundError:
    print("OBJ file not found")
except ValueError as e:
    print(f"Invalid OBJ file: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")

Texture Loading

Basic Loading:

from picogl.utils.loader.texture import TextureLoader

# Load texture
loader = TextureLoader("texture.png")
texture = loader.load()

# Use texture
texture.bind()

Advanced Loading:

# Load with options
loader = TextureLoader("texture.png")
loader.set_options(
    flip_vertical=True,
    generate_mipmaps=True,
    wrap_mode=GL_REPEAT
)

texture = loader.load()

Error Handling:

try:
    loader = TextureLoader("texture.png")
    texture = loader.load()
except FileNotFoundError:
    print("Texture file not found")
except ValueError as e:
    print(f"Invalid texture file: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")

Data Processing

Mesh Data Processing

Vertex Processing:

import numpy as np

# Normalize vertices
vertices = vertices / np.linalg.norm(vertices, axis=1, keepdims=True)

# Center vertices
center = np.mean(vertices, axis=0)
vertices = vertices - center

# Scale vertices
scale = 1.0 / np.max(np.abs(vertices))
vertices = vertices * scale

Normal Processing:

from picogl.utils.normal import compute_vertex_normals

# Generate normals
normals = compute_vertex_normals(vertices, faces)

# Normalize normals
normals = normals / np.linalg.norm(normals, axis=1, keepdims=True)

Color Processing:

# Generate colors based on normals
colors = (normals + 1.0) / 2.0

# Generate colors based on vertices
colors = (vertices - vertices.min()) / (vertices.max() - vertices.min())

# Generate random colors
colors = np.random.rand(len(vertices), 3)

Texture Processing

UV Coordinate Processing:

# Flip V coordinates
uvs[:, 1] = 1.0 - uvs[:, 1]

# Scale UV coordinates
uvs = uvs * 2.0 - 1.0

# Wrap UV coordinates
uvs = uvs % 1.0

Texture Format Conversion:

from PIL import Image

# Convert to RGBA
image = Image.open("texture.png").convert("RGBA")

# Convert to RGB
image = Image.open("texture.png").convert("RGB")

# Resize texture
image = image.resize((512, 512))

File Management

Path Handling

Path Operations:

from pathlib import Path

# Create path
base_dir = Path("resources")
texture_path = base_dir / "textures" / "texture.png"

# Check if path exists
if texture_path.exists():
    print("Texture file exists")

# Get absolute path
abs_path = texture_path.absolute()

Resource Management:

# Find resource files
def find_resources(directory, pattern="*.obj"):
    return list(Path(directory).glob(pattern))

# Load all OBJ files
obj_files = find_resources("models", "*.obj")
for obj_file in obj_files:
    loader = ObjectLoader(str(obj_file))
    data = loader.to_array_style()

Error Handling

Loading Errors

File Not Found:

try:
    loader = ObjectLoader("nonexistent.obj")
    data = loader.to_array_style()
except FileNotFoundError:
    print("File not found, using fallback")
    # Use fallback data
except Exception as e:
    print(f"Unexpected error: {e}")

Invalid File Format:

try:
    loader = ObjectLoader("invalid.obj")
    data = loader.to_array_style()
except ValueError as e:
    print(f"Invalid file format: {e}")
    # Handle gracefully
except Exception as e:
    print(f"Unexpected error: {e}")

Memory Errors:

try:
    loader = ObjectLoader("large_model.obj")
    data = loader.to_array_style()
except MemoryError:
    print("File too large, using simplified version")
    # Use simplified data
except Exception as e:
    print(f"Unexpected error: {e}")

Performance Considerations

Lazy Loading:

class LazyLoader:
    def __init__(self, filename):
        self.filename = filename
        self._data = None

    @property
    def data(self):
        if self._data is None:
            self._data = self._load_data()
        return self._data

    def _load_data(self):
        loader = ObjectLoader(self.filename)
        return loader.to_array_style()

Caching:

import functools

@functools.lru_cache(maxsize=128)
def load_texture(filename):
    loader = TextureLoader(filename)
    return loader.load()

Batch Loading:

def load_multiple_objects(filenames):
    results = []
    for filename in filenames:
        try:
            loader = ObjectLoader(filename)
            data = loader.to_array_style()
            results.append(data)
        except Exception as e:
            print(f"Failed to load {filename}: {e}")
            results.append(None)
    return results

Best Practices

  1. Use appropriate loader for your file type

  2. Handle errors gracefully with fallbacks

  3. Cache loaded data when possible

  4. Validate data before using

  5. Use lazy loading for large files

Example:

# Choose loader based on file type
if filename.endswith('.obj'):
    loader = ObjectLoader(filename)
elif filename.endswith(('.png', '.jpg', '.jpeg')):
    loader = TextureLoader(filename)
else:
    raise ValueError(f"Unsupported file type: {filename}")

# Load with error handling
try:
    data = loader.load()
except Exception as e:
    print(f"Failed to load {filename}: {e}")
    # Use fallback data
    data = create_fallback_data()

return data