import bpy
import bmesh
from mathutils.kdtree import KDTree
from mathutils import Vector
import traceback
import argparse
import sys

import os

root_folder = None
mesh_folder = None
obj_folder = None
uv_folder = None
no_uv_folder = None
cut_uv_folder = None
uncut_uv_folder = None
cut_no_uv_folder = None
uncut_no_uv_folder = None
full_folder = None
img_folder = None
file_list = None
tag_file = None

# Maximum number of object to output per input mesh
NUM_OBJECTS = 50

#https://blender.stackexchange.com/questions/150797/how-to-get-the-value-of-face-area-using-python        
# 3D tri area ABC is half the length of AB cross product AC 
def tri_area(co1, co2, co3):
    return (co2 - co1).cross( co3 - co1 ).length / 2.0

def get_uvs(o):
    """
    Get a list of UV coordinates and a mapping of face loop indices to UV coordinate indices for an object
    """
    
    # Get a mapping of vertex indices to loop (UV-per-face) index
    vert_to_loop = {}
    for face in o.data.polygons:
        for vert_idx, loop_idx in zip(face.vertices, face.loop_indices):
            if vert_idx in vert_to_loop:
                vert_to_loop[vert_idx].add(loop_idx)
            else:
                vert_to_loop[vert_idx] = {loop_idx}

    uv_coords = []
    loops_to_uv_idx = {}

    for vert_idx in vert_to_loop:
        loops = vert_to_loop[vert_idx]

        # For each vertex, collect all _unique_ UV coordinates and which loop they apply to
        # We can keep this in a dictionary because close UVs are forced to be the exact same 
        # values during the merge UV process
        coord_to_loop = {}

        for loop in loops:
            coord = o.data.uv_layers.active.data[loop].uv.copy().freeze()
            if coord in coord_to_loop:
                coord_to_loop[coord].append(loop)
            else:
                coord_to_loop[coord] = [loop]

        # Assign UV indices to loop indices
        for coord, loops in coord_to_loop.items():
            for loop in loops:
                loops_to_uv_idx[loop] = len(uv_coords)

            uv_coords.append(coord)

    return uv_coords, loops_to_uv_idx

def export_objs(objs, filename, use_uvs):
    """
    Export multiple objects in one .obj file
    """
    open(filename, "w").close()
    
    # Keep track of current base index for vertices and uv vertices as more objects are added
    v_base = 1
    uv_base = 1
    
    for o in objs:
        world_matrix = o.matrix_world
                
        # Get UV coordinates if applicable
        has_uvs = (o.data.uv_layers.active is not None) and (len(o.data.uv_layers.active.data) > 0)
        if use_uvs and has_uvs:
            uv_coords, loops_to_uv_idx = get_uvs(o)
            
        with open(filename, "a") as outfile:
            # Output vertex rows
            for vert in o.data.vertices:
                outfile.write("v " + " ".join([str(c) for c in world_matrix @ vert.co]) + "\n")
            # Output texture coordinate rows
            if use_uvs and has_uvs:
                for uv in uv_coords:
                    outfile.write("vt " + " ".join([str(c) for c in uv]) + "\n")
            # Output faces
            for face in o.data.polygons:
                if use_uvs and has_uvs:
                    # Get vertex and uv indices for this face 
                    verts_and_uvs = [(vert_idx, loops_to_uv_idx[loop_idx]) for vert_idx, loop_idx in zip(face.vertices, face.loop_indices)]
                    outfile.write("f " + " ".join([str(vert_idx + v_base) + "/" + str(uv_idx + uv_base) for vert_idx, uv_idx in verts_and_uvs]) + "\n")
                else:
                    # Output vertex indices for this face
                    outfile.write("f " + " ".join([str(vert_idx + v_base) for vert_idx in face.vertices]) + "\n")
                   
        v_base += len(o.data.vertices)
        if use_uvs and has_uvs:
            uv_base += len(uv_coords)

def export_obj(o, filename, use_uvs):
    if use_uvs:
        uv_coords, loops_to_uv_idx = get_uvs(o)
            
    with open(filename, "w") as outfile: 
        # Output vertex rows
        for vert in o.data.vertices:
            outfile.write("v " + " ".join([str(c) for c in vert.co]) + "\n")
        # Output texture coordinate rows
        if use_uvs:
            for uv in uv_coords:
                outfile.write("vt " + " ".join([str(c) for c in uv]) + "\n")
        # Output faces
        for face in o.data.polygons:
            if use_uvs:
                # Get vertex and uv indices for this face 
                verts_and_uvs = [(vert_idx, loops_to_uv_idx[loop_idx]) for vert_idx, loop_idx in zip(face.vertices, face.loop_indices)]
                outfile.write("f " + " ".join([str(vert_idx + 1) + "/" + str(uv_idx + 1) for vert_idx, uv_idx in verts_and_uvs]) + "\n")
            else:
                # Output vertex indices for this face
                outfile.write("f " + " ".join([str(vert_idx + 1) for vert_idx in face.vertices]) + "\n")

def convert_blend_to_obj(name, blend_id):
    """
    Convert an blend file into a set of cut and uncut .obj files
    """
    #https://blenderartists.org/t/how-to-apply-all-the-modifiers-with-python/1314483/2
    def apply_modifiers(obj):
        # Apply each modifier to the object
        bpy.context.view_layer.objects.active = obj
        # Try to apply modifier, remove if error
        for _, m in enumerate(obj.modifiers):
            try:
                bpy.ops.object.modifier_apply(modifier=m.name)
            except RuntimeError:
                obj.modifiers.remove(m)
        # Remove all modifiers
        for m in obj.modifiers:
            print("Removing", m)
            obj.modifiers.remove(m)

    obj_file = os.path.join(full_folder, "object_" + str(blend_id) + "_" + name + "_all.obj")
    objs = [o for o in bpy.data.objects if (o.type == "MESH") and (o.data.vertices is not None) and (o.data.polygons is not None) and (o.visible_get())]
        
    # Export a file containing every object in the "Full" folder
    export_objs(objs, obj_file, use_uvs=True)
       
    # Remove objects not in a scene or a collection - they are not visible
    no_scene_objs = []            
    print("Remove objects not in a scene or collection")
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        if len(o.users_scene) == 0 or len(o.users_collection) == 0:
            no_scene_objs.append(o)
            
    while no_scene_objs:
        bpy.data.objects.remove(no_scene_objs.pop())
        

    # Make all objects single-user, i.e. data not shared with other objects
    print("Make all objects single user")
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        print(i, "/", len(objs))
        if o.type == 'MESH' and o.visible_get():
            bpy.context.view_layer.objects.active = o
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='SELECT')
            o.select_set(True)
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.make_single_user(object=True, obdata=True) 
            
    # Apply modifiers to every object and separate into distinct objects
    print("Apply modifiers")
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        print(i, "/", len(objs))
        apply_modifiers(o)
        
    # Triangulate all meshes
    print("Triangulate meshes")
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        if o.type == 'MESH' and o.visible_get():
            print(i, "/", len(objs))
            bpy.context.view_layer.objects.active = o

            bpy.ops.object.mode_set(mode='EDIT')
            me = o.data
            bm = bmesh.from_edit_mesh(me)

            bmesh.ops.triangulate(bm, faces=bm.faces[:])
            bmesh.update_edit_mesh(me)

            bpy.ops.object.mode_set(mode = 'OBJECT')
        
    # Merge duplicate vertices
    print("Remove Doubles")
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        if o.type == 'MESH' and o.visible_get():
            bpy.context.view_layer.objects.active = o
            print("initial vertices")
            print(len(o.data.vertices))
            
            me = o.data
            me.transform(o.matrix_world)
            bm = bmesh.new()
            bm.from_mesh(me)
            
            # Construct a KD tree to efficiently query for nearby vertices
            size = len(me.vertices)
            kd = KDTree(size)

            print("Creating KD tree")
            kd_insert = kd.insert
            for i, v in enumerate(me.vertices):
                kd_insert(v.co, i)
            kd.balance()
            
            print("Looping over verts")
            merges = []
            bm.verts.ensure_lookup_table()
            uv_layer = bm.loops.layers.uv.active
            
            if uv_layer is None:
                continue
                
            # Construct groups of vertices to merge
            # Merge two vertices if they are within 1e-5 and any of their corresponding UV vertices are within 1e-4
            for v in me.vertices:
                to_merge = {bm.verts[v.index]}
                for vec, index, dist in kd.find_range(v.co, 1e-5):
                    if index != v.index:
                        uv_v = [loop[uv_layer].uv for loop in bm.verts[v.index].link_loops]
                        uv_w = [loop[uv_layer].uv for loop in bm.verts[index].link_loops]                        
    
                        any_uv_close = any((uv1 - uv2).length <= 1e-4 for uv1 in uv_v for uv2 in uv_w)
                       
                        if any_uv_close:
                            to_merge.add(bm.verts[index])
                if len(to_merge) > 1:
                    merges.append(to_merge)
                
            # Combine the groups of vertices to merge for efficiency
            print("Merging merges")
            i = 0
            while i < len(merges):
                merge = merges[i]
                combined = False
                for j in range(i):
                    prev_merge = merges[j]
                    if any(m in prev_merge for m in merge):
                        prev_merge |= merge
                        merges.remove(merge)
                        combined = True
                        break
                if not combined:
                    i += 1
                 
            # Merge groups of vertices
            print("Constructing weld map")
            weldmap = {}
            for merge in merges:
                weldto = merge.pop()
                for m in merge:
                    weldmap[m] = weldto
            
            print("Performing merge")
            bmesh.ops.weld_verts(bm, targetmap=weldmap)

            bm.to_mesh(me)
            bm.free()

            print("end vertices")
            print(len(o.data.vertices))

            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='DESELECT')

            bpy.ops.mesh.select_mode(type = 'FACE')
            bpy.ops.mesh.select_interior_faces()
            bpy.ops.mesh.delete(type='FACE')

            o.select_set(True)
            bpy.ops.mesh.separate(type='LOOSE')
            o.select_set(False)
            bpy.ops.object.mode_set(mode='OBJECT')
    
    # Remove non-mesh, invisible, or empty objects
    print("Remove empty")
    empty_objects = [o for o in bpy.data.objects if (o.type != 'MESH') or (not o.data.vertices) or (not o.data.polygons) or (not o.visible_get())]
    
    while empty_objects:
        bpy.data.objects.remove(empty_objects.pop())
        
    # Remove objects with no UVs
    print("Remove objects with no UVs")
    no_uvs = [o for o in bpy.data.objects if o.type == 'MESH' and (o.data is None or len(o.data.uv_layers) == 0)]
    
    while no_uvs:
        bpy.data.objects.remove(no_uvs.pop())
        
               
    # Remove objects whose total UV area is < 1e-6
    print("Remove objects with no UV area")
    no_uv_area = []
    
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        bm = bmesh.new()
        bm.from_mesh(o.data)
        
        bmesh.ops.triangulate(bm, faces=bm.faces)
        bm.faces.ensure_lookup_table()
        
        uv_loop = bm.loops.layers.uv[bm.loops.layers.uv.keys()[0]]
        
        total_area = 0
        for face in bm.faces:
            total_area += tri_area(*(Vector((*l[uv_loop].uv, 0)) for l in face.loops))

        if total_area < 1e-6:
            no_uv_area.append(o)
            
                
    print("Removing", len(no_uv_area), "objects")
    while no_uv_area:
        bpy.data.objects.remove(no_uv_area.pop())
        
    # Remove objects with total mesh area < 1e-8
    print("Remove objects with no mesh area")
    no_mesh_area = []
    
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        bm = bmesh.new()
        bm.from_mesh(o.data)

            
        total_area = 0
        for face in bm.faces:
            total_area += tri_area(*(Vector((v.co)) for v in face.verts))
                    
        if total_area < 1e-8:
            no_mesh_area.append(o)
        
        bm.free()
        
    print("Removing", len(no_mesh_area), "objects")
    while no_mesh_area:
        bpy.data.objects.remove(no_mesh_area.pop())
        
    # Strip objects down to only the largest NUM_OBJECTS objects
    print("Remove everything but largest")
    objs = sorted([o for o in bpy.context.scene.objects if o.type == 'MESH'], key=lambda o: len(o.data.polygons), reverse=True)
    for o in objs[NUM_OBJECTS:]:
        bpy.data.objects.remove(o)
    objs = objs[:NUM_OBJECTS] 

    # Merge close UV vertices
    print("Merge UVs")
    # Split mesh based on UV map
    # https://blender.stackexchange.com/questions/92127/python-split-mesh-by-uv-islands
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        print(i, "/", len(objs))
        if o.type == "MESH" and o.visible_get():
            bpy.context.view_layer.objects.active = o

            # Construct map of vertex indices to loop (uv-per-face) indices
            vert_to_loop = {}
            for face in o.data.polygons:
                for vert_idx, loop_idx in zip(face.vertices, face.loop_indices):
                    if vert_idx in vert_to_loop:
                        vert_to_loop[vert_idx].add(loop_idx)
                    else:
                        vert_to_loop[vert_idx] = {loop_idx}
                      
            # Construct groups of loop indices to merge
            # Merge UV vertices that share a vertex and are within 1e-6 of each other
            for vert_idx in vert_to_loop:
                merges = []
                loop_list = list(vert_to_loop[vert_idx])
                for i, l1 in enumerate(loop_list):
                    uv_1 = o.data.uv_layers.active.data[l1].uv
                    merge = {l1}
                    for l2 in loop_list[:i]:
                        uv_2 = o.data.uv_layers.active.data[l2].uv
                        if (uv_1 - uv_2).length < 1e-6:
                            merge.add(l2)
                    if len(merge) > 1:
                        merges.append(merge)
                
                # Combine merges for efficency
                i = 0
                while i < len(merges):
                    merge = merges[i]
                    combined = False
                    for j in range(i):
                        prev_merge = merges[j]
                        if any(m in prev_merge for m in merge):
                            prev_merge |= merge
                            merges.remove(merge)
                            combined = True
                            break
                    if not combined:
                        i += 1
                 
                # Merge UV vertices
                # In blender, this is done by setting their coordinates equal to each other
                for merge in merges:
                    merge_list = list(merge)
                    shared_coords = o.data.uv_layers.active.data[merge_list[0]].uv
                    for i in merge_list[1:]:
                        o.data.uv_layers.active.data[i].uv = shared_coords

    bpy.ops.object.select_all(action='DESELECT')

    # Export uncut meshes to Uncut folder at this stage
    objs = sorted([o for o in bpy.context.scene.objects], key=lambda o: len(o.data.polygons), reverse=True)
    print("Export Uncut")
    for i, o in enumerate(objs):
        print(i, "/", len(objs))
        bpy.context.view_layer.objects.active = o
        o.select_set(True)
        
        # Calculate mesh tags
        euler_characteristic = len(o.data.polygons) - len(o.data.edges) + len(o.data.vertices)
        is_disk = (euler_characteristic == 1)
        
        bm = bmesh.new()
        bm.from_mesh(o.data)
        
        is_closed = all(not edge.is_boundary for edge in bm.edges)
        
        has_non_manifold_verts = any(not vert.is_manifold for vert in bm.verts)
        has_non_manifold_edges = any(not edge.is_manifold and not edge.is_boundary for edge in bm.edges)
        
        is_manifold = not has_non_manifold_verts and not has_non_manifold_edges
        
        is_small = len(o.data.polygons) < 100

        # Perform exports
        filename = "object_" + str(blend_id) + "_" + name + "_" + str(i) + ".obj"
        obj_file = os.path.join(uncut_uv_folder, filename)
        export_obj(o, obj_file, use_uvs=True)
        
        obj_file = os.path.join(uncut_no_uv_folder, filename)
        export_obj(o, obj_file, use_uvs=False)
        
        # Emit tags
        with open(tag_file, "a") as f:
            f.write(",".join(["Uncut", filename, str(blend_id), name, str(i), str(is_disk), str(is_closed), str(is_manifold), str(is_small)]) + "\n")
            
        o.select_set(False)
        
        
    # Split objects based on where UV cuts are     
    print("Split on UVs")
    # Split mesh based on UV map
    # https://blender.stackexchange.com/questions/92127/python-split-mesh-by-uv-islands
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        print(i, "/", len(objs))
        if o.type == "MESH" and o.visible_get():
            bpy.context.view_layer.objects.active = o

            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.uv.select_all(action='SELECT')
            
            # Mark seams on mesh from boundaries in UV space
            bpy.ops.uv.seams_from_islands(mark_seams=True)
            bpy.ops.object.mode_set(mode='OBJECT')
            
            bpy.ops.object.select_all(action='SELECT')
            bpy.ops.object.mode_set(mode='EDIT')
            
            bm = bmesh.from_edit_mesh(o.data)
            bm.edges.ensure_lookup_table()
                   
            seams = [e for e in bm.edges if e.seam]
             
            # Split mesh on it seams
            bmesh.ops.split_edges(bm, edges=seams)
            bmesh.update_edit_mesh(o.data)
            o.data.update()
            
            # Separate mesh into separate objects by loose parts
            bpy.ops.uv.select_all(action='DESELECT')
            bpy.ops.object.mode_set(mode='OBJECT')
            bpy.ops.object.mode_set(mode='EDIT')
            o.select_set(True)
            bpy.ops.mesh.separate(type='LOOSE')
            o.select_set(False)
            bpy.ops.object.mode_set(mode='OBJECT')
            
    # Remove objects with no UV area
    # Objects previously had guaranteed UV area, but now have been split into 
    # components that might not
    print("Remove objects with no UV area")
    no_uv_area = []
    
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        bm = bmesh.new()
        bm.from_mesh(o.data)
        
        bmesh.ops.triangulate(bm, faces=bm.faces)
        bm.faces.ensure_lookup_table()
        
        uv_loop = bm.loops.layers.uv[bm.loops.layers.uv.keys()[0]]
        
        total_area = 0
        for face in bm.faces:
            total_area += tri_area(*(Vector((*l[uv_loop].uv, 0)) for l in face.loops))
    
        if total_area < 1e-6:
            no_uv_area.append(o)
            
    print("Removing", len(no_uv_area), "objects")
    while no_uv_area:
        bpy.data.objects.remove(no_uv_area.pop())
            
    # Remove objects with no mesh area
    # Objects previously had guaranteed mesh area, but now have been split into
    # components that might not
    print("Remove objects with no mesh area")
    no_mesh_area = []
    
    objs = list(enumerate(bpy.data.objects))
    for i, o in objs:
        bm = bmesh.new()
        bm.from_mesh(o.data)

        total_area = 0
        for face in bm.faces:
            total_area += tri_area(*(Vector((v.co)) for v in face.verts))
            
        if total_area < 1e-8:
            no_mesh_area.append(o)
        
        bm.free()
        
    print("Removing", len(no_mesh_area), "objects")
    while no_mesh_area:
        bpy.data.objects.remove(no_mesh_area.pop())
     
    # Remove non-mesh, empty, or invisible objects
    print("Remove empty")
    empty_objects = [o for o in bpy.data.objects if (o.type != 'MESH') or (not o.data.vertices) or (not o.data.polygons) or (not o.visible_get())]
                 
    while empty_objects:
        bpy.data.objects.remove(empty_objects.pop())
         
    # Remove non-manifold objects for the cut portion of the dataset
    print("Remove nonmanifold objects")
    non_manifold_objs = []
    for i, o in enumerate(bpy.data.objects):
        bm = bmesh.new()
        bm.from_mesh(o.data)
        
        # Check both edge-manifoldness and vertex-manifoldness
        has_non_manifold_verts = any(not vert.is_manifold for vert in bm.verts)
        has_non_manifold_edges = any(not edge.is_manifold and not edge.is_boundary for edge in bm.edges)
                
        if has_non_manifold_verts or has_non_manifold_edges:
            non_manifold_objs.append(o)
            
    print("Removing", len(non_manifold_objs), "non-manifold objs")
    for o in non_manifold_objs:
        bpy.data.objects.remove(o)
       
    # Strip down objects to only the largest NUM_OBJECTS objects    
    print("Remove everything but largest")
    objs = sorted([o for o in bpy.context.scene.objects if o.type == 'MESH'], key=lambda o: len(o.data.polygons), reverse=True)
    for o in objs[NUM_OBJECTS:]:
        bpy.data.objects.remove(o)
        
    # Remove any vertices not referenced by the faces
    print("Remove unreferenced vertices")
    for i, o in enumerate(bpy.data.objects):    
        bpy.context.view_layer.objects.active = o
        bpy.ops.object.mode_set(mode='EDIT')
        
        me = o.data
        bm = bmesh.from_edit_mesh(me)

        bm.faces.ensure_lookup_table()
        
        referenced_vert_idxs = {v.index for face in bm.faces for v in face.verts}
        unreferenced_verts = [v for v in bm.verts if v.index not in referenced_vert_idxs]
        
        bmesh.ops.delete(bm, geom=unreferenced_verts)
        
        bmesh.update_edit_mesh(me)
        o.data.update()
        
        bpy.ops.object.mode_set(mode='OBJECT')
        
    objs = sorted([o for o in bpy.context.scene.objects], key=lambda o: len(o.data.polygons), reverse=True)
        
    # Check that connectivity of UV map = connectivity of mesh
    # Discard if not
    print("Checking connectivity")
    invalid_connectivity = []
    for i, o in enumerate(objs):
        bm = bmesh.new()
        bm.from_mesh(o.data)

        bm.faces.ensure_lookup_table()
        uv_loop = bm.loops.layers.uv[bm.loops.layers.uv.keys()[0]]
        
        bm.verts.ensure_lookup_table()
        
        not_same =  False
        for vert in bm.verts:
            if len(vert.link_loops) == 0:
                not_same = True
                break
            for l1 in vert.link_loops:
                for l2 in vert.link_loops:
                    if (l1[uv_loop].uv - l2[uv_loop].uv).length > 1e-4:
                        not_same = True
                        break
                        
        if not_same:
            print("Mesh", i, "has invalid connectivity")
            invalid_connectivity.append(o)
            
    print("Removing", len(invalid_connectivity), "invalid objs")
    for o in invalid_connectivity:
        bpy.data.objects.remove(o)
        
    bpy.ops.object.select_all(action='DESELECT')

    # Export objects for Cut portion of dataset
    objs = sorted([o for o in bpy.context.scene.objects], key=lambda o: len(o.data.polygons), reverse=True)
    print("Export")
    for i, o in enumerate(objs):
        print(i, "/", len(objs))
        bpy.context.view_layer.objects.active = o
        o.select_set(True)
        
        # Calculate dataset tags
        euler_characteristic = len(o.data.polygons) - len(o.data.edges) + len(o.data.vertices)
        is_disk = (euler_characteristic == 1)
        
        bm = bmesh.new()
        bm.from_mesh(o.data)
        
        is_closed = all(not edge.is_boundary for edge in bm.edges)
        
        has_non_manifold_verts = any(not vert.is_manifold for vert in bm.verts)
        has_non_manifold_edges = any(not edge.is_manifold and not edge.is_boundary for edge in bm.edges)
        
        is_manifold = not has_non_manifold_verts and not has_non_manifold_edges
        
        is_small = len(o.data.polygons) < 100
        
        # Perform export
        filename = "object_" + str(blend_id) + "_" + name + "_" + str(i) + ".obj"
        obj_file = os.path.join(cut_uv_folder, filename)
        export_obj(o, obj_file, use_uvs=True)
        
        obj_file = os.path.join(cut_no_uv_folder, filename)
        export_obj(o, obj_file, use_uvs=False)
        
        # Emit tags
        with open(tag_file, "a") as f:
            f.write(",".join(["Cut", filename, str(blend_id), name, str(i), str(is_disk), str(is_closed), str(is_manifold), str(is_small)]) + "\n")

        o.select_set(False)

def convert_blend(f, n):
    name, ext = os.path.splitext(f)

    # Import file into blender and convert to .obj
    try:
        if ext == ".blend":
            bpy.ops.wm.open_mainfile(filepath=os.path.join(mesh_folder, f))
            convert_blend_to_obj(name, n)
        elif ext == ".fbx":
            bpy.ops.wm.read_homefile(use_empty=True)
            bpy.ops.import_scene.fbx(filepath=os.path.join(mesh_folder, f))
            convert_blend_to_obj(name, n) 
        elif ext == ".gltf" or ext == ".glb":
            bpy.ops.wm.read_homefile(use_empty=True)
            bpy.ops.import_scene.gltf(filepath=os.path.join(mesh_folder, f))
            convert_blend_to_obj(name, n) 
        elif ext == ".dae":
            bpy.ops.wm.read_homefile(use_empty=True)
            bpy.ops.wm.collada_import(filepath=os.path.join(mesh_folder, f), 
                  auto_connect = True, 
                  find_chains = True, 
                  fix_orientation = True) 
            convert_blend_to_obj(name, n)
        elif ext == ".obj":
            bpy.ops.wm.read_homefile(use_empty=True)
            bpy.ops.import_scene.obj(filepath=os.path.join(mesh_folder, f))
            convert_blend_to_obj(name, n) 
    except Exception as e:
        print(f, "did not work")
        print(e)
            

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Convert file to obj")
    parser.add_argument("-r", "--root-folder", type=str, required=True, dest="root_folder")
    parser.add_argument("-f", "--file", type=str, required=True, dest="filename")
    parser.add_argument("-n", "--number", type=int, required=True, dest="number")

    argv = sys.argv
    
    if "--" not in argv:
        argv = []
    else:
        argv = argv[argv.index("--") + 1:]
    
    args = parser.parse_args(argv)
    
    # Get paths to relevant folders for dataset
    root_folder = os.path.abspath(args.root_folder)
    mesh_folder = os.path.join(root_folder, "Mesh_Files")
    obj_folder = os.path.join(root_folder, "Obj_Files")
        
    uv_folder = os.path.join(obj_folder, "Artist_UVs")
    no_uv_folder = os.path.join(obj_folder, "No_UVs")
    
    cut_uv_folder = os.path.join(uv_folder, "Cut")
    uncut_uv_folder = os.path.join(uv_folder, "Uncut")
    
    cut_no_uv_folder = os.path.join(no_uv_folder, "Cut")
    uncut_no_uv_folder = os.path.join(no_uv_folder, "Uncut")

    img_folder = os.path.join(root_folder, "Img_Files")
    full_folder = os.path.join(obj_folder, "Full")

    file_list = os.path.join(obj_folder, "processed_files.txt")
    tag_file = os.path.join(obj_folder, "dataset_tags.csv")

    # Convert file to blend
    convert_blend(args.filename, args.number)