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