Complete guide to PyHelios core functionality, API reference, and advanced usage patterns.
Introduction
What is Helios?
Helios is a powerful C++ library for 3D physical simulation of plant and environmental systems. It provides:
- 3D Geometry Management: Sophisticated handling of complex plant architectures
- Physical Modeling: Advanced simulation of light, energy, and mass transport
- Plugin Architecture: Extensible system for specialized modeling capabilities
- High Performance: Optimized C++ core with optional GPU acceleration
PyHelios Integration
PyHelios provides seamless Python access to Helios functionality while maintaining:
- Performance: Direct access to optimized C++ implementations
- Flexibility: Full access to underlying Helios functionality
- Ease of Use: Pythonic interfaces and error handling
- Cross-Platform: Consistent behavior across Windows, macOS, and Linux
Core Architecture
Context - The Simulation Environment
The Context is the central hub of any Helios simulation:
- Geometry Management: Stores and organizes all 3D primitives
- Coordinate Systems: Manages spatial reference frames
- Data Association: Links data to geometric elements
- State Tracking: Maintains simulation state and history
Primitives - Basic Geometric Elements
Helios uses several primitive types:
- Patches: Rectangular surface elements (most common)
- Triangles: Triangular surface elements for complex shapes
- Tiles: Specialized surface elements
UUID-Based Object Tracking
All primitives are identified by Universally Unique Identifiers (UUIDs):
from pyhelios import Context
context = Context()
patch_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
triangle_uuid = context.addTriangle(
vertex0=vec3(0, 0, 0),
vertex1=vec3(1, 0, 0),
vertex2=vec3(0.5, 1, 0)
)
area = context.getPrimitiveArea(patch_uuid)
vertices = context.getPrimitiveVertices(triangle_uuid)
Data Types and Imports
Standardized Vector Type Imports
PyHelios provides convenient access to vector types. Use this standardized pattern throughout your code:
from pyhelios import Context
context = Context()
position = vec3(1.0, 2.0, 3.0)
size = vec2(10.0, 5.0)
direction = vec4(1.0, 2.0, 3.0, 1.0)
grid_size = int2(100, 50)
voxel_index = int3(10, 20, 5)
tensor_dims = int4(100, 50, 25, 10)
green = RGBcolor(0.2, 0.8, 0.2)
transparent_blue = RGBAcolor(0.2, 0.2, 0.8, 0.5)
spherical = SphericalCoord(
radius=10.0,
elevation=0.5,
azimuth=1.57
)
Alternative (verbose) approach:
from pyhelios import DataTypes
position = DataTypes.vec3(1.0, 2.0, 3.0)
color = DataTypes.RGBcolor(0.3, 0.7, 0.2)
Available Types
The star import from pyhelios.types import *
provides:
Context Class Reference
The Context class is the central simulation environment. All methods documented here are verified from the actual implementation.
Basic Usage
from pyhelios import Context
context = Context()
with Context() as context:
patch_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
Geometry Creation Methods
Patches (Rectangular Surfaces)
patch_uuid = context.addPatch()
patch_uuid = context.addPatch(
center=vec3(0, 0, 0),
size=vec2(1.0, 2.0)
)
patch_uuid = context.addPatch(
center=vec3(0, 0, 0),
size=vec2(1.0, 2.0),
rotation=SphericalCoord(radius=1, elevation=0, azimuth=0),
color=RGBcolor(0.3, 0.7, 0.2)
)
Triangles
triangle_uuid = context.addTriangle(
vertex0=vec3(0, 0, 0),
vertex1=vec3(1, 0, 0),
vertex2=vec3(0.5, 1, 0)
)
triangle_uuid = context.addTriangle(
vertex0=vec3(0, 0, 0),
vertex1=vec3(1, 0, 0),
vertex2=vec3(0.5, 1, 0),
color=RGBcolor(0.8, 0.2, 0.2)
)
Compound Geometry
Tiles (Subdivided Patches)
tile_uuids = context.addTile(
center=vec3(0, 0, 0),
size=vec2(2, 2),
rotation=SphericalCoord(1, 0, 0),
subdivisions=int2(4, 4)
)
print(f"Created {len(tile_uuids)} patches in tile")
Spheres (Tessellated)
sphere_uuids = context.addSphere(
center=vec3(0, 0, 2),
radius=1.0,
ndivs=10,
color=RGBcolor(0.2, 0.5, 0.8)
)
print(f"Created sphere with {len(sphere_uuids)} triangular faces")
Tubes (Cylindrical Geometry)
nodes = [vec3(0, 0, 0), vec3(0, 0, 1), vec3(0, 0, 2)]
radii = [0.1, 0.15, 0.05]
tube_uuids = context.addTube(
nodes=nodes,
radii=radii,
ndivs=8,
colors=RGBcolor(0.4, 0.2, 0.1)
)
print(f"Created tube with {len(tube_uuids)} primitives")
Boxes (3D Rectangular)
box_uuids = context.addBox(
center=vec3(1, 1, 1),
size=vec3(0.5, 0.5, 0.5),
rotation=SphericalCoord(1, 0, 0.785),
color=RGBcolor(0.6, 0.3, 0.8)
)
print(f"Created box with {len(box_uuids)} faces")
Array-Based Methods
Textured Triangles
textured_uuid = context.addTriangleTextured(
vertex0=vec3(0, 0, 0),
vertex1=vec3(1, 0, 0),
vertex2=vec3(0.5, 1, 0),
textureFilename="textures/leaf.jpg",
uv0=vec2(0, 0),
uv1=vec2(1, 0),
uv2=vec2(0.5, 1)
)
Batch Triangle Creation from Arrays
import numpy as np
vertices = np.array([
[0, 0, 0], [1, 0, 0], [0.5, 1, 0],
[1, 0, 0], [2, 0, 0], [1.5, 1, 0]
], dtype=np.float32)
triangle_uuids = context.addTrianglesFromArrays(
vertices=vertices,
indices=np.array([0, 1, 2, 3, 4, 5], dtype=np.uint32),
color=RGBcolor(0.5, 0.7, 0.3)
)
print(f"Created {len(triangle_uuids)} triangles from arrays")
Batch Textured Triangle Creation
texture_coords = np.array([
[0, 0], [1, 0], [0.5, 1],
[0, 0], [1, 0], [0.5, 1]
], dtype=np.float32)
textured_uuids = context.addTrianglesFromArraysTextured(
vertices=vertices,
indices=np.array([0, 1, 2, 3, 4, 5], dtype=np.uint32),
textureCoords=texture_coords,
textureFilename="textures/bark.jpg"
)
print(f"Created {len(textured_uuids)} textured triangles")
Geometry Query Methods
Basic Properties
area = context.getPrimitiveArea(uuid)
normal = context.getPrimitiveNormal(uuid)
vertices = context.getPrimitiveVertices(uuid)
prim_type = context.getPrimitiveType(uuid)
color = context.getPrimitiveColor(uuid)
total_primitives = context.getPrimitiveCount()
all_uuids = context.getAllUUIDs()
Data Association
context.setPrimitiveDataFloat(uuid, "temperature", 25.0)
context.setPrimitiveDataString(uuid, "material", "leaf")
temperature = context.getPrimitiveData(uuid, "temperature", float)
material = context.getPrimitiveData(uuid, "material", str)
has_data = context.doesPrimitiveDataExist(uuid, "temperature")
import numpy as np
all_uuids = context.getAllUUIDs()
temperature_array = context.getPrimitiveDataArray(all_uuids, "temperature")
print(f"Temperature array shape: {temperature_array.shape}")
print(f"Mean temperature: {np.mean(temperature_array)}")
selected_uuids = all_uuids[:100]
temperature_subset = context.getPrimitiveDataArray(selected_uuids, "temperature")
area_array = context.getPrimitiveDataArray(all_uuids, "area")
Primitive Collections
all_uuids = context.getAllUUIDs()
print(f"Total primitives: {len(all_uuids)}")
for uuid in all_uuids:
area = context.getPrimitiveArea(uuid)
prim_type = context.getPrimitiveType(uuid)
File I/O Operations
Supported File Formats
Geometry Files
- PLY: Stanford Triangle Format (preferred for 3D meshes)
- OBJ: Wavefront OBJ format
- XML: Helios XML format for complex scenes
Data Files
- CSV: Comma-separated values for tabular data
- TXT: Plain text data files
- JSON: Configuration and metadata
Loading Geometry
PLY Files
from pyhelios import Context
context = Context()
uuids = context.loadPLY("models/plant.ply")
print(f"Loaded {len(uuids)} primitives from PLY file")
origin = vec3(5, 0, 0)
rotation = SphericalCoord(1.0, 0, 1.57)
color = RGBcolor(0.3, 0.7, 0.3)
uuids = context.loadPLY(
"leaf.ply",
origin=origin,
rotation=rotation,
color=color
)
OBJ Files
uuids = context.loadOBJ("models/tree.obj")
uuids = context.loadOBJ(
"textured_plant.obj",
silent=True
)
XML Files
uuids = context.loadXML("scenes/plant_scene.xml")
uuids = context.loadXML(
"complex_scene.xml",
origin=vec3(10, 0, 0),
rotation=SphericalCoord(1.0, 0.1, 0)
)
Writing Geometry
PLY Files
from pyhelios import Context
context = Context()
patch_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
sphere_uuids = context.addSphere(center=vec3(2, 0, 0), radius=0.5, ndivs=10)
context.writePLY("output/scene.ply")
selected_uuids = [patch_uuid] + sphere_uuids[:5]
context.writePLY("output/subset.ply", UUIDs=selected_uuids)
OBJ Files
context.writeOBJ("output/scene.obj")
context.writeOBJ("output/scene_with_normals.obj", write_normals=True)
context.writeOBJ("output/subset.obj", UUIDs=[uuid1, uuid2, uuid3])
context.setPrimitiveDataFloat(patch_uuid, "temperature", 25.0)
context.setPrimitiveDataFloat(patch_uuid, "area", 1.0)
context.writeOBJ(
"output/with_data.obj",
UUIDs=[patch_uuid],
primitive_data_fields=["temperature", "area"]
)
context.writeOBJ("output/quiet.obj", silent=True)
Error Handling and Best Practices
Exception Handling
from pyhelios import Context, HeliosPluginNotAvailableError
try:
context = Context()
patch_uuid = context.addPatch(center=vec3(0, 0, 0), size=vec2(1, 1))
context.setPrimitiveDataFloat(patch_uuid, "temperature", 25.0)
temp = context.getPrimitiveData(patch_uuid, "temperature", float)
except HeliosPluginNotAvailableError as e:
print(f"Plugin not available: {e}")
except Exception as e:
print(f"Simulation error: {e}")
Memory Management
with Context() as context:
uuids = context.loadPLY("large_model.ply")
pass
context = Context()
try:
pass
finally:
pass
Performance Optimization
from pyhelios import Context
context = Context()
patch_centers = [vec3(i, j, 0) for i in range(10) for j in range(10)]
patch_uuids = []
for center in patch_centers:
uuid = context.addPatch(center=center, size=vec2(0.1, 0.1))
patch_uuids.append(uuid)
for uuid in patch_uuids:
context.setPrimitiveDataFloat(uuid, "temperature", 20.0 + random.uniform(-5, 5))
print(f"Total primitives: {context.getPrimitiveCount()}")
Integration with Helios Ecosystem
PyHelios seamlessly integrates with the broader Helios ecosystem:
- Native Helios: Access to full C++ API through ctypes
- Helios Plugins: Support for all 21+ available plugins
- Helios Data Formats: Compatible file I/O operations
- Helios Documentation: Consistent with native documentation
Plugin System Integration
from pyhelios import Context
context = Context()
print_plugin_status()
registry = get_plugin_registry()
if registry.is_plugin_available('radiation'):
print("GPU radiation modeling available")
if registry.is_plugin_available('visualizer'):
print("3D visualization available")
if registry.is_plugin_available('weberpenntree'):
print("Procedural tree generation available")
Native Pointer Access
For advanced users needing direct C++ API access:
native_context_ptr = context.getNativePtr()
Physical Units and Conventions
Coordinate System
- X-axis: East (positive) / West (negative)
- Y-axis: North (positive) / South (negative)
- Z-axis: Up (positive) / Down (negative)
Units
- Length: Meters (m)
- Area: Square meters (m²)
- Angles: Radians
- Temperature: Celsius (°C)
- Radiation: W/m² or mol/m²/s depending on context
Color Values
- RGB: 0.0 to 1.0 range (not 0-255)
- Alpha: 0.0 (transparent) to 1.0 (opaque)
Complete Example
from pyhelios import Context, WeberPennTree, RadiationModel
try:
with Context() as context:
print("Creating plant geometry...")
ground = context.addPatch(
center=vec3(0, 0, 0),
size=vec2(10, 10),
color=RGBcolor(0.4, 0.3, 0.2)
)
try:
from pyhelios import WeberPennTree
wpt = WeberPennTree(context)
tree_uuid = wpt.buildTree(WeberPennTree.WPTType.LEMON)
print(f"Created tree: {tree_uuid}")
except Exception as e:
print(f"WeberPennTree not available: {e}")
try:
leaf_uuids = context.loadPLY("models/leaf.ply")
print(f"Loaded {len(leaf_uuids)} leaf primitives")
except:
print("Leaf model not found, continuing without it")
all_uuids = context.getAllUUIDs()
for uuid in all_uuids:
context.setPrimitiveDataFloat(uuid, "temperature", 20.0)
try:
with RadiationModel(context) as radiation:
radiation.addRadiationBand("SW")
radiation.setDirectRayCount("SW", 100)
radiation.runBand("SW")
print("Radiation simulation completed")
except Exception as e:
print(f"Radiation modeling not available: {e}")
print(f"Results available for {len(all_uuids)} primitives")
print(f"Simulation completed with {context.getPrimitiveCount()} primitives")
except Exception as e:
print(f"Simulation failed: {e}")
For complete documentation of the underlying Helios library, visit: https://baileylab.ucdavis.edu/software/helios