bl_info = {
    "name": "Assets Collector",
    "blender": (3, 3, 3),
    "category": "Scene",
}

import bpy
import os
import shutil

DEBUG = False

class AssetCollectorPanel(bpy.types.Panel):
    bl_label = "Asset Collector"
    bl_idname = "OBJECT_PT_asset_collector"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"
    bl_category = "Asset Collector"

    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.label(text="Assets folder name")
        row.prop(context.scene, "asset_folder_name", text="")
        row = layout.row()
        row.operator("asset.collect", text="Collect")

class AssetCollectOperator(bpy.types.Operator):
    bl_idname = "asset.collect"
    bl_label = "Collect Assets"

    def execute(self, context):
        if DEBUG:
            print("DEBUGGING: Assets folder name is", context.scene.asset_folder_name)
            return {'FINISHED'}
        else:
            # Initialize an empty set to store unique file paths
            file_paths = set()

            # Iterate over all objects in the scene
            for obj in bpy.data.objects:
                # Check if the object has a material
                if obj.material_slots:
                    # Iterate over all materials assigned to the object
                    for material_slot in obj.material_slots:
                        # Check if the material has a node tree
                        if material_slot.material.node_tree:
                            # Check if the material uses a texture
                            for node in material_slot.material.node_tree.nodes:
                                if node.type == "TEX_IMAGE" and node.image:
                                    # Get the file path of the image and add it to the set
                                    file_path = node.image.filepath
                                    file_paths.add(file_path)

            # Iterate over all scenes in the blend file
            for scene in bpy.data.scenes:
                # Check if the scene has a linked library
                if scene.library:
                    # Get the file path of the linked library and add it to the set
                    file_path = scene.library.filepath
                    file_paths.add(file_path)

            # Iterate over all Alembic cache files in the blend file
            for cache_file in bpy.data.cache_files:
                # Get the file path of the Alembic cache file and add it to the set
                file_path = cache_file.filepath
                file_paths.add(file_path)

            # Get the blend file's directory
            blend_dir = os.path.dirname(bpy.data.filepath)

            # Create the assets folder in the blend file's directory
            assets_dir = os.path.join(blend_dir, context.scene.asset_folder_name)
            os.makedirs(assets_dir, exist_ok=True)

            # Copy each file in file_paths to the assets folder
            num_files = 0
            for file_path in file_paths:
                # Get the real file path
                file_path = os.path.realpath(bpy.path.abspath(file_path))
                
                # Check if the file exists
                if not os.path.exists(file_path):
                    print("File", file_path, "does not exist. Skipping...")
                    continue
                
                # Get the destination file path
                dest_path = os.path.join(assets_dir, os.path.basename(file_path))
                
                # Try to copy the file
                try:
                    shutil.copy2(file_path, dest_path)
                    num_files += 1
                except PermissionError as e:
                    print("Failed to copy file", file_path, "due to permission error. Skipping...")
                    continue

            # Print the report to the console
            print("Copied", num_files, "files from the following locations:")
            for file_path in file_paths:
                print("-", file_path)

            for obj in bpy.data.objects:
                if obj.material_slots:
                    for material_slot in obj.material_slots:
                        if material_slot.material.node_tree:
                            for node in material_slot.material.node_tree.nodes:
                                if node.type == "TEX_IMAGE" and node.image:
                                    old_path = node.image.filepath
                                    node.image.filepath = os.path.join(assets_dir, os.path.basename(old_path))

            for scene in bpy.data.scenes:
                if scene.library:
                    old_path = scene.library.filepath
                    scene.library.filepath = os.path.join(assets_dir, os.path.basename(old_path))

            for cache_file in bpy.data.cache_files:
                old_path = cache_file.filepath
                cache_file.filepath = os.path.join(assets_dir, os.path.basename(old_path))
                

            # Making paths relative
            for obj in bpy.data.objects:
                if obj.material_slots:
                    for material_slot in obj.material_slots:
                        if material_slot.material.node_tree:
                            for node in material_slot.material.node_tree.nodes:
                                if node.type == "TEX_IMAGE" and node.image:
                                    node.image.filepath = bpy.path.relpath(node.image.filepath)

            for scene in bpy.data.scenes:
                if scene.library:
                    scene.library.filepath = bpy.path.relpath(scene.library.filepath)

            for cache_file in bpy.data.cache_files:
                cache_file.filepath = bpy.path.relpath(cache_file.filepath)
            

classes = [    AssetCollectorPanel,    AssetCollectOperator]

def register():
    bpy.types.Scene.asset_folder_name = bpy.props.StringProperty(
        name="Assets folder name",
        default="assets"
    )
    for cls in classes:
        bpy.utils.register_class(cls)

def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.asset_folder_name

if __name__ == "__main__":
    register()

blender_asset_collector_add-on.py