feat: add 1.0.0 version for blender 4.4.3
This commit is contained in:
177
__init__.py
Normal file
177
__init__.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
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()
|
||||||
11
blender_manifest.toml
Normal file
11
blender_manifest.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
schema_version = "1.0.0"
|
||||||
|
id = "etncl_consolidation_process"
|
||||||
|
name = "ETNCL - Consolidation Process"
|
||||||
|
version = "1.0.0"
|
||||||
|
tagline = "Fully consolidate a Blender file by packaging its external data"
|
||||||
|
maintainer = "Florian Pineau <florian@etincelle-color.com>"
|
||||||
|
type = "add-on"
|
||||||
|
tags = ["Pipeline"]
|
||||||
|
blender_version_min = "4.4.3"
|
||||||
|
license = ["SPDX:GPL-3.0-or-later"]
|
||||||
|
copyright = ["2025 ETINCELLE POST-PRODUCTION"]
|
||||||
Reference in New Issue
Block a user