Files

177 lines
7.1 KiB
Python

import bpy
import os
import shutil
def get_external_datablocks():
"""Return a list of external data-blocks"""
out = []
for attr in dir(bpy.data):
collections = getattr(bpy.data, attr)
if isinstance(collections, type(bpy.data.objects)):
for data_block in collections:
if (isinstance(data_block, bpy.types.Image) or isinstance(data_block, bpy.types.MovieClip)) and hasattr(data_block, "filepath"):
if hasattr(data_block, "packed_file"):
print(f"[{'P' if data_block.packed_file is not None else ' '}] [{data_block.__class__}] {data_block.source}: {data_block.filepath}")
else:
print(f" [{data_block.__class__}] {data_block.source}: {data_block.filepath}")
out.append(data_block)
return out
def collect_data(data_block, dirpath, sequence_in_subfolder=True):
"""Copy data into a given folder. Only work for 'MOVIE' and 'SEQUENCE' data-blocks"""
if data_block.source == "MOVIE":
source_path = os.path.normpath(bpy.path.abspath(data_block.filepath))
dest_path = os.path.join(dirpath, os.path.basename(source_path))
print(f"Copying FROM '{source_path}' TO '{dest_path}'")
shutil.copyfile(source_path, dest_path)
elif data_block.source == "SEQUENCE":
source_dirpath = os.path.dirname(os.path.normpath(bpy.path.abspath(data_block.filepath)))
if sequence_in_subfolder:
dest_dirpath = os.path.join(dirpath, os.path.basename(source_dirpath))
else:
dest_dirpath = dirpath
if not os.path.isdir(dest_dirpath):
os.mkdir(dest_dirpath)
print(f"Copying FROM '{source_dirpath}' TO '{dest_dirpath}'")
for filename in os.listdir(source_dirpath):
if os.path.splitext(filename)[1] != ".db":
shutil.copyfile(os.path.join(source_dirpath, filename), os.path.join(dest_dirpath, filename))
def get_source_datablocks(datablocks, source):
"""Return the list data-blocks which are of specific sources"""
out = []
for data_block in datablocks:
if data_block.source == source:
out.append(data_block)
return out
def main():
external_datablocks = get_external_datablocks()
print(f"{len(external_datablocks)} external data-blocks")
bl_dirpath = os.path.dirname(bpy.data.filepath)
bl_filename = os.path.splitext(os.path.basename(bpy.data.filepath))[0]
if len(external_datablocks) > 0:
# Create a subfolder per blend file
external_data_dirpath = os.path.join(bl_dirpath, f"{bl_filename}-external_data")
if not os.path.isdir(external_data_dirpath):
os.mkdir(external_data_dirpath)
for data_block in external_datablocks:
collect_data(data_block, external_data_dirpath)
class WM_OT_ConsolidateProject(bpy.types.Operator):
"""Open the consolidation dialog box"""
bl_label = "Consolidate Project"
bl_idname = "wm.consolidate_project"
my_purge_unused_data: bpy.props.BoolProperty(name="Purge Unused Data", default=True)
my_pack_ressources: bpy.props.BoolProperty(name="Pack External Ressources", default=True)
my_collect_movies: bpy.props.BoolProperty(name="Collect Movies", default=True)
my_collect_sequences: bpy.props.BoolProperty(name="Collect Image Sequences", default=True)
my_sequences_in_subfolders: bpy.props.BoolProperty(name="Save Image Sequences in Sub-Folders", default=False)
my_external_datablocks = []
my_external_movies = []
my_external_sequences = []
my_dirpath = None
my_filename = None
my_external_data_dirpath = None
def execute(self, context):
if self.my_purge_unused_data:
bpy.ops.outliner.orphans_purge()
if self.my_pack_ressources:
bpy.ops.file.pack_all()
if self.my_collect_movies:
for data_block in self.my_external_movies:
collect_data(data_block, self.my_external_data_dirpath)
if self.my_collect_sequences:
for data_block in self.my_external_sequences:
collect_data(data_block, self.my_external_data_dirpath, self.my_sequences_in_subfolders)
self.report({'INFO'}, "Consolidation complete")
return {"FINISHED"}
def invoke(self, context, event):
self.my_external_datablocks = get_external_datablocks()
self.my_external_movies = get_source_datablocks(self.my_external_datablocks, source='MOVIE')
self.my_external_sequences = get_source_datablocks(self.my_external_datablocks, source='SEQUENCE')
self.my_dirpath = os.path.dirname(bpy.data.filepath)
self.my_filename = os.path.splitext(os.path.basename(bpy.data.filepath))[0]
self.my_external_data_dirpath = os.path.join(self.my_dirpath, f"{self.my_filename}-external_data")
if not os.path.isdir(self.my_external_data_dirpath):
os.mkdir(self.my_external_data_dirpath)
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
col = layout.column()
col.label(text="Cleaning and Packing Ressources", icon="PACKAGE")
row = col.row()
row.prop(self, "my_purge_unused_data")
row = col.row()
row.prop(self, "my_pack_ressources")
layout.separator()
col = layout.column()
col.label(text="Collect movies and image sequences", icon="FILE_MOVIE")
row = col.row()
row.prop(self, "my_collect_movies")
row = col.row()
row.prop(self, "my_collect_sequences")
row = col.row()
row.prop(self, "my_sequences_in_subfolders", icon="NEWFOLDER")
row.enabled = self.my_collect_sequences
box = layout.box()
box.label(text="External data-blocks")
for data_block in self.my_external_datablocks:
row = box.row()
display_data_block(data_block, row)
def display_data_block(data_block, layout):
split = layout.split(factor=.9)
if data_block.source in ["SEQUENCE", "MOVIE"]:
split.label(text=f"{data_block.name}", icon="FILE_MOVIE")
#split.label(text="", icon="WARNING_LARGE")
else:
split.label(text=f"{data_block.name}", icon="FILE_IMAGE")
if hasattr(data_block, "packed_file"):
if data_block.packed_file is not None:
split.label(text=f"", icon="PACKAGE")
else:
split.label(text=f"", icon="UGLYPACKAGE")
def menu_func_import(self, context):
self.layout.operator(WM_OT_ConsolidateProject.bl_idname, text="ETNCL - Consolidate", icon="PACKAGE")
self.layout.separator()
blender_classes = [
WM_OT_ConsolidateProject
]
def register():
for cls in blender_classes:
bpy.utils.register_class(cls)
bpy.types.TOPBAR_MT_file_external_data.prepend(menu_func_import)
def unregister():
for cls in blender_classes:
bpy.utils.unregister_class(cls)
bpy.types.TOPBAR_MT_file_external_data.remove(menu_func_import)
if __name__ == "__main__":
register()