-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path_CompleteOptimizationScript.py
More file actions
232 lines (196 loc) · 8.28 KB
/
_CompleteOptimizationScript.py
File metadata and controls
232 lines (196 loc) · 8.28 KB
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import bpy
from datetime import date
from os import system
cls = lambda: system('cls')
# Cache a reference to all selected objects.
objs = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH']
# Switch to Object Mode, as this script will only work in that context.
bpy.ops.object.mode_set(mode='OBJECT')
# GLOBALS
finalReport = ""
totalVertsRemoved = 0
#region Helpers
#########################
# Utility to give us the option of showing a popup around the user's cursor for warnings/errors.
#########################
def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'):
def draw(self, context):
self.layout.label(text=message)
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
#########################
# Outputs the prefix to the blender terminal before we begin optimizing.
#########################
def OutputReportPrefixToTerminal():
# Add some space between terminal outputs.
for x in range(6):
print("")
for x in range(4):
print("###########################################")
print("")
print("'Quality is never an accident. It is always the result of intelligent effort' \n- John Ruskin")
print("")
print("###########################################")
print("")
print("Begin Optimizing {}....".format(bpy.path.basename(bpy.context.blend_data.filepath)))
print("")
for x in range(1):
print("###########################################")
print("")
#########################
# Outputs the final report to the blender terminal after we have optimized the selected objects.
#########################
def PrintFinalReport():
global finalReport
global totalVertsRemoved
today = date.today()
print("")
for x in range(2):
print("###########################################")
print("")
print("FINAL REPORT")
print("")
print("Date: {}".format(today))
print("")
for x in range(1):
print("###########################################")
print("")
print(finalReport)
print("TOTAL VERTICES REMOVED: {}".format(totalVertsRemoved))
print("")
for x in range(4):
print("###########################################")
#########################
# Clears out the blender terminal of all previous text output.
#########################
def ClearTerminal():
cls()
#########################
# Cleans up any orphan material data that still exists in the blend file after the
# selected objects have been optimized. ie. Yellow.001 material.
#########################
def PurgeOrphanMaterialData():
for block in bpy.data.materials:
if block.users == 0:
bpy.data.materials.remove(block)
#endregion
#region Tasks
#########################
# Assign original material to all instances of a duplicate material
# Example: Any object with "Material.001" assigned would have it replaced with "Material"
#########################
def RemoveDuplicateMaterials():
global finalReport
mats = bpy.data.materials
# Loop through all selected objects.
for obj in objs:
# For each material slot on the selected object.
for slt in obj.material_slots:
# split the material name into different parts.
part = slt.name.rpartition('.')
# if part 2 is numeric (001, 002, etc) and part 0 (Yellow) exists in our materials list...
if part[2].isnumeric() and part[0] in mats:
# search for any faces using the duplicate material. ie. Yellow.001
faces = [x for x in obj.data.polygons if x.material_index == obj.material_slots.find(slt.name)]
# if we found any faces...
if (len(faces) > 0):
for f in faces:
finalReport += "Re-assigned duplicated material faces to original material in {} \n".format(obj.name)
# reassign to the original mat (Yellow)
f.material_index = obj.material_slots.find(part[0])
#########################
# Automatically remove materials from selected objects that aren't assigned to any faces
#########################
def RemoveUnusedMaterials(obj):
global finalReport
count = len(obj.material_slots)
#print("Old Material Slot Count: {}".format(count))
bpy.context.view_layer.objects.active = obj
bpy.ops.object.material_slot_remove_unused()
newCount = len(obj.material_slots)
#print("New Material Slot Count: {}".format(newCount))
if (count != newCount):
finalReport += "Removed unused materials in {} \n".format(obj.name)
#########################
# Remove ALL Empty(unnamed) Material Slots from Selected Objects
# Example: Unnamed material slots are removed from all selected objects and only the named materials remain.
#########################
def RemoveEmptyMaterialSlots(obj):
global finalReport
for x in obj.material_slots:
if x.name == "":
bpy.context.view_layer.objects.active = obj
obj.active_material_index = x.slot_index
bpy.ops.object.material_slot_remove()
finalReport += "Removed empty materials in {} \n".format(obj.name)
#########################
# Re-order Materials by Name to Alphabetical Order
# Example: BEFORE: blue, white, red, green, black. AFTER: black, blue, green, red, white
#########################
def ApplyAlphabeticalOrderToMaterials(obj):
mats = [mat.name for mat in obj.material_slots]
mats.sort()
for i, mat_name in enumerate(mats):
# set active material slot to end slot
obj.active_material_index = len(obj.material_slots)-1
while obj.active_material.name != mat_name:
obj.active_material_index -=1
while obj.active_material_index > i:
bpy.ops.object.material_slot_move(direction='UP')
#########################
# Merge vertices based on their proximity to optimize total vert count.
#########################
def MergeByDistance(obj):
global finalReport
global totalVertsRemoved
mergeByDistanceThreshold = 0.0001
# Cache a quick reference to the current vert count so we can compare later.
oldVertCount = len(obj.data.vertices);
# Remove Doubles (Merge By Distance)
bpy.ops.object.select_all(action='DESELECT') # deselect all object
bpy.context.view_layer.objects.active = obj
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles(threshold = mergeByDistanceThreshold)
bpy.ops.object.editmode_toggle()
newVertCount = len(obj.data.vertices);
vertCountDifference = oldVertCount - newVertCount;
totalVertsRemoved += vertCountDifference;
if (oldVertCount != newVertCount):
finalReport += "Merge by Distance removed {difference} vertices in {name} \n".format(name=obj.name, difference=vertCountDifference)
#########################
# Remove any objects that have 0 vertices from the scene.
#########################
def RemoveZeroVertObjectsFromScene():
scene = bpy.context.scene
empty_meshobs = [o for o in scene.objects
if o.type == 'MESH'
and not o.data.vertices]
while empty_meshobs:
bpy.data.objects.remove(empty_meshobs.pop())
#endregion
#region Init
#########################
# Runs a series of methods to optimize the blend file and selected objects.
#########################
def RunOptimizations():
# Run through any pre-processors first.
OutputReportPrefixToTerminal()
RemoveDuplicateMaterials()
# Run the main loop through all selected objects and do work.
for obj in objs:
RemoveUnusedMaterials(obj)
RemoveEmptyMaterialSlots(obj)
ApplyAlphabeticalOrderToMaterials(obj)
MergeByDistance(obj)
# Finalize by purging any orphan material data.
PurgeOrphanMaterialData()
RemoveZeroVertObjectsFromScene()
# If the user has not selected any objects, throw an error.
if (len(bpy.context.selected_objects) <= 0):
ShowMessageBox("You must select at least one object in the scene.", "Automate Blender Scripts - Optimization Script", 'ERROR')
else:
ClearTerminal()
# GO GO TILE OPTIMIZATIONS!
RunOptimizations()
PrintFinalReport()
#endregion