-
Notifications
You must be signed in to change notification settings - Fork 0
/
__init__.py
149 lines (124 loc) · 5.95 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# Gather Resources - Gathers all resources used in the project and copies them to a local textures folder.
# Copyright (C) 2024 Simon Heggie
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# You can provide feedback, report bugs, or submit suggestions via the issue section:
# https://github.com/SimonHeggie/Blender-GatherResources/issues
#
# If this program operates interactively, it should output the following:
#
# Gather Resources Copyright (C) 2024 Simon Heggie
# This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
# This is free software, and you are welcome to redistribute it
# under certain conditions; type `show c' for details.
#
# The commands `show w` and `show c` should display relevant parts of the GNU GPL.
bl_info = {
"name": "Gather Resources",
"blender": (4, 2, 0),
"category": "File",
"version": (0, 3, 1, "alpha"),
"author": "Simon Heggie",
"description": "Gathers all resources used in the project to a local textures folder.",
"location": "File > External Data",
"warning": "Pre-alpha testing",
"wiki_url": "https://github.com/SimonHeggie/Blender-GatherResources/blob/main/README.md",
"tracker_url": "https://github.com/SimonHeggie/Blender-GatherResources/issues",
"license": "GPL",
}
# (rest of the code follows here)
import bpy
import shutil
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
class GatherResourcesOperator(bpy.types.Operator):
"""Gather resources and copy to the 'textures/' folder within the .blend file's directory"""
bl_idname = "file.gather_resources"
bl_label = "Gather Resources"
bl_options = {'REGISTER', 'UNDO'}
def copy_file(self, src, dest):
"""Helper function to copy files with error handling."""
try:
if not dest.exists() or src.stat().st_mtime > dest.stat().st_mtime:
shutil.copy2(src, dest)
self.report({'INFO'}, f"Collected: {src} -> {dest}")
return src.name, True
except PermissionError as e:
self.report({'ERROR'}, f"Error copying {src}: {e}")
return src.name, False
def get_destination_folder(self, src, textures_dir):
"""Determine destination folder to prevent naming conflicts."""
if textures_dir in src.parents:
return textures_dir # Place in root if already inside textures/
folder_name = src.parent.name
folder_path = textures_dir / folder_name
folder_path.mkdir(parents=True, exist_ok=True)
return folder_path
def execute(self, context):
# Check if the blend file has been saved
blend_filepath = bpy.data.filepath
if not blend_filepath:
self.report({'ERROR'}, "Please save your blend file before gathering resources.")
return {'CANCELLED'}
# Set destination directory relative to the saved .blend file
blend_dir = Path(bpy.path.abspath("//"))
textures_dir = blend_dir / "textures"
textures_dir.mkdir(parents=True, exist_ok=True)
tasks = []
def add_task_for_path(file_path, strip=None):
"""Add a copy task for a given file path, checking strip type if needed."""
if not file_path: # Ensure the path is valid
return
# Check if it's a SoundSequence and use strip.sound.filepath if available
if strip and hasattr(strip, 'sound'):
file_path = strip.sound.filepath
src = Path(bpy.path.abspath(file_path))
if src.exists(): # Ensure the file exists
dest = self.get_destination_folder(src, textures_dir) / src.name
tasks.append(executor.submit(self.copy_file, src, dest))
else:
self.report({'WARNING'}, f"File not found: {src}")
with ThreadPoolExecutor() as executor:
# Process all images used in shaders and textures
for image in bpy.data.images:
add_task_for_path(image.filepath)
# Process all media files in VSE
for scene in bpy.data.scenes:
if scene.sequence_editor:
for strip in scene.sequence_editor.sequences_all:
add_task_for_path(strip.filepath if hasattr(strip, 'filepath') else None, strip=strip)
# Process all cache files in object modifiers
for obj in bpy.data.objects:
for mod in obj.modifiers:
if mod.type == 'MESH_SEQUENCE_CACHE' and mod.cache_file:
add_task_for_path(mod.cache_file.filepath)
# Process results and print summary
collected_files = {"TOTAL": 0}
for future in as_completed(tasks):
filename, success = future.result()
if success:
collected_files["TOTAL"] += 1
self.report({'INFO'}, f"Gathering Complete: {collected_files['TOTAL']} files collected.")
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator(GatherResourcesOperator.bl_idname, text="Gather Resources")
def register():
bpy.utils.register_class(GatherResourcesOperator)
bpy.types.TOPBAR_MT_file_external_data.prepend(menu_func) # Use prepend to insert at the top
def unregister():
bpy.utils.unregister_class(GatherResourcesOperator)
bpy.types.TOPBAR_MT_file_external_data.remove(menu_func)
if __name__ == "__main__":
register()