Source code for moderngl_window.loaders.scene.wavefront

import logging
import os

import numpy

import pywavefront
from pywavefront import cache
from pywavefront.obj import ObjParser

import moderngl
from moderngl_window.loaders.base import BaseLoader
from moderngl_window.opengl.vao import VAO
from moderngl_window import resources
from moderngl_window.resources.decorators import texture_dirs
from moderngl_window.meta import SceneDescription, TextureDescription
from moderngl_window.scene import Material, MaterialTexture, Mesh, Node, Scene
from moderngl_window.exceptions import ImproperlyConfigured
from moderngl_window.geometry.attributes import AttributeNames

logger = logging.getLogger(__name__)

def translate_buffer_format(vertex_format: str, attr_names: AttributeNames):
    """Translate the buffer format"""
    buffer_format = []
    attributes = []
    mesh_attributes = []

    if "T2F" in vertex_format:
        mesh_attributes.append(("TEXCOORD_0", attr_names.TEXCOORD_0, 2))

    if "C3F" in vertex_format:
        mesh_attributes.append(("COLOR_0", attr_names.COLOR_0, 3))

    if "N3F" in vertex_format:
        mesh_attributes.append(("NORMAL", attr_names.NORMAL, 3))

    mesh_attributes.append(("POSITION", attr_names.POSITION, 3))

    return " ".join(buffer_format), attributes, mesh_attributes

class VAOCacheLoader(cache.CacheLoader):
    """Load geometry data directly into vaos"""

    attr_names = None

    def load_vertex_buffer(self, fd, material, length):
        buffer_format, attributes, mesh_attributes = translate_buffer_format(
            material.vertex_format, self.attr_names

        vao = VAO(, mode=moderngl.TRIANGLES)
        vao.buffer(, buffer_format, attributes)

        setattr(material, "vao", vao)
        setattr(material, "buffer_format", buffer_format)
        setattr(material, "attributes", attributes)
        setattr(material, "mesh_attributes", mesh_attributes)

ObjParser.cache_loader_cls = VAOCacheLoader

class Loader(BaseLoader):
    """Loade wavefront/obj files"""

    kind = "wavefront"
    file_extensions = [
        [".obj", ".gz"],

[docs] def __init__(self, meta: SceneDescription): super().__init__(meta)
[docs] def load(self): """Loads a wavefront/obj file including materials and textures Returns: Scene: The Scene instance """ path = self.find_scene(self.meta.path)"loading %s", path) if not path: raise ImproperlyConfigured("Scene '{}' not found".format(self.meta.path)) if path.suffix == ".bin": path = path.parent / path.stem VAOCacheLoader.attr_names = self.meta.attr_names data = pywavefront.Wavefront( str(path), create_materials=True, cache=self.meta.cache ) scene = Scene(self.meta.resolved_path) texture_cache = {} for _, mat in data.materials.items(): mesh = Mesh( # Traditional loader if mat.vertices: buffer_format, attributes, mesh_attributes = translate_buffer_format( mat.vertex_format, self.meta.attr_names ) vbo = numpy.array(mat.vertices, dtype="f4") vao = VAO(, mode=moderngl.TRIANGLES) vao.buffer(vbo, buffer_format, attributes) mesh.vao = vao for attrs in mesh_attributes: mesh.add_attribute(*attrs) # Binary cache loader elif hasattr(mat, "vao"): mesh = Mesh( mesh.vao = mat.vao for attrs in mat.mesh_attributes: mesh.add_attribute(*attrs) else: # Empty continue scene.meshes.append(mesh) mesh.material = Material( scene.materials.append(mesh.material) mesh.material.color = mat.diffuse if mat.texture: # A texture can be referenced multiple times, so we need to cache loaded ones texture = texture_cache.get(mat.texture.path) if not texture: # HACK: pywavefront only give us an absolute path rel_path = os.path.relpath(mat.texture.find(), str(path.parent))"Loading: %s", rel_path) with texture_dirs([path.parent]): texture = resources.textures.load( TextureDescription( label=rel_path, path=rel_path, mipmap=True, anisotropy=16.0, ) ) texture_cache[rel_path] = texture mesh.material.mat_texture = MaterialTexture( texture=texture, sampler=None, ) node = Node(mesh=mesh) scene.root_nodes.append(node) # Not supported yet for obj # self.calc_scene_bbox() scene.prepare() return scene