Python bindings for the Open Asset Import Library (ASSIMP) enabling 3D model loading, processing, and export
—
Complete scene graph representation including nodes, meshes, materials, textures, animations, cameras, lights, and metadata. PyAssimp provides hierarchical access to all 3D scene components with Python-friendly data structures and optional NumPy array integration.
Root container for all 3D scene data and resources.
class Scene:
"""
Root scene container holding all 3D data.
Attributes:
- rootnode: Node, root node of scene hierarchy
- meshes: list, list of Mesh objects
- materials: list, list of Material objects
- textures: list, list of embedded Texture objects
- animations: list, list of Animation sequences
- cameras: list, list of Camera objects
- lights: list, list of Light objects
"""
rootnode: Node
meshes: list
materials: list
textures: list
animations: list
cameras: list
lights: listUsage examples:
import pyassimp
scene = pyassimp.load("model.dae")
# Access scene components
print(f"Scene has {len(scene.meshes)} meshes")
print(f"Scene has {len(scene.materials)} materials")
print(f"Scene has {len(scene.textures)} embedded textures")
print(f"Scene has {len(scene.animations)} animations")
# Traverse scene hierarchy
def print_node_hierarchy(node, depth=0):
indent = " " * depth
print(f"{indent}Node: {node.name}")
print(f"{indent} Meshes: {len(node.meshes)}")
print(f"{indent} Children: {len(node.children)}")
for child in node.children:
print_node_hierarchy(child, depth + 1)
print_node_hierarchy(scene.rootnode)
pyassimp.release(scene)Hierarchical scene graph nodes containing transformation data and mesh references.
class Node:
"""
Scene graph node representing spatial hierarchy.
Attributes:
- name: str, node name/identifier
- transformation: array, 4x4 transformation matrix (numpy array if available)
- parent: Node, parent node (None for root)
- children: list, child Node objects
- meshes: list, mesh references/indices for this node
"""
name: str
transformation: array
parent: Node
children: list
meshes: listUsage examples:
import pyassimp
import numpy as np
scene = pyassimp.load("model.dae")
def process_node(node):
print(f"Processing node: {node.name}")
# Access transformation matrix
if hasattr(node, 'transformation'):
transform = node.transformation
print(f"Transform matrix shape: {transform.shape if hasattr(transform, 'shape') else len(transform)}")
# Decompose transformation if needed
if isinstance(transform, np.ndarray):
# NumPy array - can do matrix operations
position = transform[:3, 3] # Translation component
print(f"Position: {position}")
# Process node meshes
for mesh_ref in node.meshes:
mesh = scene.meshes[mesh_ref] if isinstance(mesh_ref, int) else mesh_ref
print(f" Mesh: {len(mesh.vertices)} vertices")
# Recursively process children
for child in node.children:
process_node(child)
process_node(scene.rootnode)
pyassimp.release(scene)3D mesh geometry data including vertices, faces, normals, texture coordinates, and vertex attributes.
class Mesh:
"""
3D mesh data container.
Attributes:
- vertices: array, vertex positions (Nx3)
- normals: array, vertex normals (Nx3)
- faces: array, face indices
- texturecoords: array, UV coordinates (per texture channel)
- colors: array, vertex colors (per color channel)
- tangents: array, tangent vectors (Nx3)
- bitangents: array, bitangent vectors (Nx3)
- materialindex: int, index into scene materials list
- name: str, mesh name
"""
vertices: array
normals: array
faces: array
texturecoords: array
colors: array
tangents: array
bitangents: array
materialindex: int
name: strUsage examples:
import pyassimp
import numpy as np
scene = pyassimp.load("model.obj", processing=pyassimp.postprocess.aiProcess_Triangulate)
for i, mesh in enumerate(scene.meshes):
print(f"Mesh {i}: {mesh.name}")
# Vertex data
print(f" Vertices: {len(mesh.vertices)} ({mesh.vertices.shape if hasattr(mesh.vertices, 'shape') else 'list'})")
# Face data
print(f" Faces: {len(mesh.faces)}")
if mesh.faces:
face = mesh.faces[0]
print(f" First face indices: {face.indices if hasattr(face, 'indices') else face}")
# Normal data
if hasattr(mesh, 'normals') and mesh.normals is not None:
print(f" Normals: {len(mesh.normals)}")
# UV coordinates
if hasattr(mesh, 'texturecoords') and mesh.texturecoords is not None:
uv_channels = len(mesh.texturecoords) if isinstance(mesh.texturecoords, list) else 1
print(f" UV channels: {uv_channels}")
# Vertex colors
if hasattr(mesh, 'colors') and mesh.colors is not None:
color_channels = len(mesh.colors) if isinstance(mesh.colors, list) else 1
print(f" Color channels: {color_channels}")
# Material reference
print(f" Material index: {mesh.materialindex}")
if mesh.materialindex < len(scene.materials):
material = scene.materials[mesh.materialindex]
print(f" Material properties: {len(material.properties)}")
pyassimp.release(scene)Polygon face definitions with vertex indices.
class Face:
"""
Polygon face with vertex indices.
Attributes:
- indices: list, vertex indices forming this face
"""
indices: listUsage examples:
import pyassimp
scene = pyassimp.load("model.ply", processing=pyassimp.postprocess.aiProcess_Triangulate)
mesh = scene.meshes[0]
print(f"Mesh has {len(mesh.faces)} faces")
# Access face data
for i, face in enumerate(mesh.faces[:5]): # First 5 faces
if hasattr(face, 'indices'):
indices = face.indices
else:
indices = face # May be direct list after processing
print(f"Face {i}: vertices {indices}")
# Access vertex positions for this face
if hasattr(indices, '__iter__'):
for vertex_idx in indices:
if vertex_idx < len(mesh.vertices):
vertex = mesh.vertices[vertex_idx]
print(f" Vertex {vertex_idx}: {vertex}")
pyassimp.release(scene)Animation sequences and keyframe data.
class Animation:
"""
Animation sequence data.
Attributes:
- name: str, animation name
- duration: float, animation duration
- tickspersecond: float, time units per second
- channels: list, animation channels for different nodes
"""
name: str
duration: float
tickspersecond: float
channels: listUsage examples:
import pyassimp
scene = pyassimp.load("animated_model.dae")
if scene.animations:
for i, animation in enumerate(scene.animations):
print(f"Animation {i}: {animation.name}")
print(f" Duration: {animation.duration}")
print(f" Ticks per second: {animation.tickspersecond}")
print(f" Channels: {len(animation.channels)}")
# Process animation channels
for channel in animation.channels:
print(f" Channel node: {channel.nodename if hasattr(channel, 'nodename') else 'unknown'}")
pyassimp.release(scene)3D camera definitions with projection parameters.
class Camera:
"""
3D camera definition.
Attributes:
- name: str, camera name
- position: Vector3D, camera position
- lookat: Vector3D, look-at target direction
- up: Vector3D, up vector
- horizontalfov: float, horizontal field of view
- aspect: float, aspect ratio
- clipplanenear: float, near clipping plane
- clipplanefar: float, far clipping plane
"""
name: str
position: Vector3D
lookat: Vector3D
up: Vector3D
horizontalfov: float
aspect: float
clipplanenear: float
clipplanefar: floatUsage examples:
import pyassimp
scene = pyassimp.load("scene_with_camera.dae")
if scene.cameras:
for i, camera in enumerate(scene.cameras):
print(f"Camera {i}: {camera.name}")
if hasattr(camera, 'position'):
pos = camera.position
print(f" Position: ({pos.x}, {pos.y}, {pos.z})")
if hasattr(camera, 'horizontalfov'):
print(f" FOV: {camera.horizontalfov} radians")
if hasattr(camera, 'aspect'):
print(f" Aspect ratio: {camera.aspect}")
pyassimp.release(scene)Scene lighting definitions.
class Light:
"""
Light source definition.
Attributes:
- name: str, light name
- type: int, light type (directional, point, spot, etc.)
- position: Vector3D, light position
- direction: Vector3D, light direction
- colorambient: Color3D, ambient color
- colordiffuse: Color3D, diffuse color
- colorspecular: Color3D, specular color
- attenuation*: float, attenuation parameters
- angle*: float, spotlight angle parameters
"""
name: str
type: int
position: Vector3D
direction: Vector3D
colorambient: Color3D
colordiffuse: Color3D
colorspecular: Color3DUsage examples:
import pyassimp
scene = pyassimp.load("lit_scene.dae")
if scene.lights:
for i, light in enumerate(scene.lights):
print(f"Light {i}: {light.name}")
if hasattr(light, 'type'):
print(f" Type: {light.type}")
if hasattr(light, 'colordiffuse'):
color = light.colordiffuse
print(f" Diffuse color: ({color.r}, {color.g}, {color.b})")
pyassimp.release(scene)Key-value metadata storage for additional scene information.
class Metadata:
"""
Key-value metadata container.
Attributes:
- keys: list, metadata key names
- values: list, MetadataEntry objects with typed values
"""
keys: list
values: list
class MetadataEntry:
"""
Individual metadata entry with type information.
Attributes:
- type: int, data type identifier
- data: any, typed data value
Type Constants:
- AI_BOOL = 0, boolean value
- AI_INT32 = 1, 32-bit integer
- AI_UINT64 = 2, 64-bit unsigned integer
- AI_FLOAT = 3, floating point number
- AI_DOUBLE = 4, double precision float
- AI_AISTRING = 5, string value
- AI_AIVECTOR3D = 6, 3D vector
"""
type: int
data: any
AI_BOOL = 0
AI_INT32 = 1
AI_UINT64 = 2
AI_FLOAT = 3
AI_DOUBLE = 4
AI_AISTRING = 5
AI_AIVECTOR3D = 6Usage examples:
import pyassimp
scene = pyassimp.load("model_with_metadata.dae")
# Check for metadata on scene root
if hasattr(scene.rootnode, 'metadata') and scene.rootnode.metadata:
metadata = scene.rootnode.metadata
print(f"Metadata entries: {len(metadata.keys)}")
for key, value_entry in zip(metadata.keys, metadata.values):
print(f" {key}: {value_entry.data} (type: {value_entry.type})")
pyassimp.release(scene)import pyassimp
import numpy as np
scene = pyassimp.load("model.obj")
for mesh in scene.meshes:
# Direct access to vertex arrays
vertices = mesh.vertices
if isinstance(vertices, np.ndarray):
# NumPy array - efficient operations
centroid = np.mean(vertices, axis=0)
bounds_min = np.min(vertices, axis=0)
bounds_max = np.max(vertices, axis=0)
print(f"Centroid: {centroid}")
print(f"Bounds: {bounds_min} to {bounds_max}")
else:
# Python list - slower but compatible
if vertices:
centroid = [sum(v[i] for v in vertices)/len(vertices) for i in range(3)]
print(f"Centroid: {centroid}")
pyassimp.release(scene)import pyassimp
def analyze_scene(filename):
scene = pyassimp.load(filename)
stats = {
'nodes': 0,
'meshes': len(scene.meshes),
'materials': len(scene.materials),
'textures': len(scene.textures),
'animations': len(scene.animations),
'cameras': len(scene.cameras),
'lights': len(scene.lights),
'total_vertices': 0,
'total_faces': 0
}
def count_nodes(node):
stats['nodes'] += 1
for child in node.children:
count_nodes(child)
count_nodes(scene.rootnode)
for mesh in scene.meshes:
stats['total_vertices'] += len(mesh.vertices) if mesh.vertices else 0
stats['total_faces'] += len(mesh.faces) if mesh.faces else 0
pyassimp.release(scene)
return stats
# Usage
stats = analyze_scene("complex_model.dae")
for key, value in stats.items():
print(f"{key}: {value}")import pyassimp
def load_geometry_only(filename):
"""Load only geometry data, skip animations, cameras, etc."""
# Use component removal to skip unwanted data
processing = (pyassimp.postprocess.aiProcess_Triangulate |
pyassimp.postprocess.aiProcess_RemoveComponent)
# Note: RemoveComponent needs configuration via AI_CONFIG_PP_RVC_FLAGS
# This is a simplified example
scene = pyassimp.load(filename, processing=processing)
# Extract only needed data
geometry_data = []
for mesh in scene.meshes:
mesh_data = {
'vertices': mesh.vertices,
'faces': mesh.faces,
'normals': getattr(mesh, 'normals', None),
'material_idx': mesh.materialindex
}
geometry_data.append(mesh_data)
pyassimp.release(scene)
return geometry_dataInstall with Tessl CLI
npx tessl i tessl/pypi-pyassimp