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:
objectObject Loader Class
- 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:
objectLoads a 2D texture from a DDS file or a standard image file using PIL. Automatically creates an OpenGL texture ID.
- 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.
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 orNone): 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.
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
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
Use appropriate loader for your file type
Handle errors gracefully with fallbacks
Cache loaded data when possible
Validate data before using
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