Source code for moderngl_window.loaders.scene.wavefront

import io
import logging
import os
from pathlib import Path

import moderngl
import numpy
import pywavefront
from pywavefront import cache
from pywavefront.obj import ObjParser

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

logger = logging.getLogger(__name__)


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

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

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

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

    buffer_format.append("3f")
    attributes.append(attr_names.POSITION)
    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: AttributeNames

    def load_vertex_buffer(
        self, fd: io.TextIOWrapper, material: pywavefront.material.Material, length: int
    ) -> None:
        buffer_format, attributes, mesh_attributes = translate_buffer_format(
            material.vertex_format, self.attr_names
        )

        vao = VAO(material.name, mode=moderngl.TRIANGLES)
        # FIXME: Are we actually reading from text or byte stream here?
        vao.buffer(fd.read(length), 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


[docs] class Loader(BaseLoader): """Load wavefront/obj files""" kind = "wavefront" file_extensions = [ [".obj"], [".obj", ".gz"], [".bin"], ] meta: SceneDescription def __init__(self, meta: SceneDescription): super().__init__(meta)
[docs] def load(self) -> Scene: """Loads a wavefront/obj file including materials and textures Returns: Scene: The Scene instance """ path = self.find_scene(Path(self.meta.path if self.meta.path is not None else "")) logger.info("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.as_posix() if self.meta.resolved_path is not None else "" ) texture_cache: dict[str, pywavefront.material.Material] = {} for _, mat in data.materials.items(): mesh = Mesh(mat.name) # 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(mat.name, 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(mat.name) mesh.vao = mat.vao for attrs in mat.mesh_attributes: mesh.add_attribute(*attrs) else: # Empty continue scene.meshes.append(mesh) mesh.material = Material(mat.name) 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)) logger.info("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