"""Provides `load_obj`, which allows to load different 3D object files. """
import os
import re
from typing import List, Optional, Dict
import bpy
from blenderproc.python.types.MeshObjectUtility import MeshObject, convert_to_meshes
from blenderproc.python.utility.Utility import Utility
from blenderproc.python.material.MaterialLoaderUtility import create_material_from_texture
from blenderproc.python.material.MaterialLoaderUtility import create as create_material
[docs]
def load_obj(filepath: str, cached_objects: Optional[Dict[str, List[MeshObject]]] = None,
             use_legacy_obj_import: bool = False, **kwargs) -> List[MeshObject]:
    """ Import all objects for the given file and returns the loaded objects
    In .obj files a list of objects can be saved in.
    In .ply files only one object can be saved so the list has always at most one element
    :param filepath: the filepath to the location where the data is stored
    :param cached_objects: a dict of filepath to objects, which have been loaded before, to avoid reloading
                           (the dict is updated in this function)
    :param use_legacy_obj_import: If this is true the old legacy obj importer in python is used. It is slower, but
                                  it correctly imports the textures in the ShapeNet dataset.
    :param kwargs: all other params are handed directly to the bpy loading fct. check the corresponding documentation
    :return: The list of loaded mesh objects.
    """
    if not os.path.exists(filepath):
        raise FileNotFoundError(f"The given filepath does not exist: {filepath}")
    if cached_objects is not None and isinstance(cached_objects, dict):
        if filepath in cached_objects.keys():
            created_obj = []
            for obj in cached_objects[filepath]:
                # duplicate the object
                created_obj.append(obj.duplicate())
            return created_obj
        loaded_objects = load_obj(filepath, cached_objects=None, **kwargs)
        cached_objects[filepath] = loaded_objects
        return loaded_objects
    # save all selected objects
    previously_selected_objects = bpy.context.selected_objects
    if filepath.endswith(".obj"):
        # load an .obj file:
        if use_legacy_obj_import:
            bpy.ops.import_scene.obj(filepath=filepath, **kwargs)
        else:
            bpy.ops.wm.obj_import(filepath=filepath, **kwargs)
    elif filepath.endswith(".ply"):
        PLY_TEXTURE_FILE_COMMENT = "comment TextureFile "
        model_name = os.path.basename(filepath)
        # Read file
        with open(filepath, "r", encoding="latin-1") as file:
            ply_file_content = file.read()
        # Check if texture file is given
        if PLY_TEXTURE_FILE_COMMENT in ply_file_content:
            # Find name of texture file
            texture_file_name = re.search(f"{PLY_TEXTURE_FILE_COMMENT}(.*)\n", ply_file_content).group(1)
            # Determine full texture file path
            texture_file_path = os.path.join(os.path.dirname(filepath), texture_file_name)
            material = create_material_from_texture(
                texture_file_path, material_name=f"ply_{model_name}_texture_model"
            )
            # Change content of ply file to work with blender ply importer
            new_ply_file_content = ply_file_content
            new_ply_file_content = new_ply_file_content.replace("property float texture_u", "property float s")
            new_ply_file_content = new_ply_file_content.replace("property float texture_v", "property float t")
            # Create temporary .ply file
            tmp_ply_file = os.path.join(Utility.get_temporary_directory(), model_name)
            with open(tmp_ply_file, "w", encoding="latin-1") as file:
                file.write(new_ply_file_content)
            # Load .ply mesh
            bpy.ops.import_mesh.ply(filepath=tmp_ply_file, **kwargs)
        else:  # If no texture was given
            # load a .ply mesh
            bpy.ops.import_mesh.ply(filepath=filepath, **kwargs)
            # Create default material
            material = create_material('ply_material')
            material.map_vertex_color()
        selected_objects = [obj for obj in bpy.context.selected_objects if obj not in previously_selected_objects]
        for obj in selected_objects:
            obj.data.materials.append(material.blender_obj)
    elif filepath.endswith('.dae'):
        bpy.ops.wm.collada_import(filepath=filepath)
    elif filepath.lower().endswith('.stl'):
        # load a .stl file
        bpy.ops.wm.stl_import(filepath=filepath, **kwargs)
        # add a default material to stl file
        mat = bpy.data.materials.new(name="stl_material")
        mat.use_nodes = True
        selected_objects = [obj for obj in bpy.context.selected_objects if
                                obj not in previously_selected_objects]
        for obj in selected_objects:
            obj.data.materials.append(mat)
    elif filepath.lower().endswith('.fbx'):
        bpy.ops.import_scene.fbx(filepath=filepath)
    elif filepath.lower().endswith('.glb') or filepath.lower().endswith('.gltf'):
        bpy.ops.import_scene.gltf(filepath=filepath)
    mesh_objects = convert_to_meshes([obj for obj in bpy.context.selected_objects
                                  if obj not in previously_selected_objects])
    # Add model_path cp to all objects
    for obj in mesh_objects:
        obj.set_cp("model_path", filepath)
    return mesh_objects