Merge branch 'cb/fixs'
[plumiferos.git] / release / scripts / mesh_cleanup.py
blob1eb3e3968b2b89241923a4256dbc9313d78ced55
1 #!BPY
2 """
3 Name: 'Clean Meshes'
4 Blender: 242
5 Group: 'Mesh'
6 Tooltip: 'Clean unused data from all selected mesh objects.'
7 """
9 __author__ = ["Campbell Barton"]
10 __url__ = ("blender", "elysiun", "http://members.iinet.net.au/~cpbarton/ideasman/")
11 __version__ = "0.1"
12 __bpydoc__ = """\
13 Clean Meshes
15 Cleans unused data from selected meshes
16 """
18 # ***** BEGIN GPL LICENSE BLOCK *****
20 # Script copyright (C) Campbell J Barton
22 # This program is free software; you can redistribute it and/or
23 # modify it under the terms of the GNU General Public License
24 # as published by the Free Software Foundation; either version 2
25 # of the License, or (at your option) any later version.
27 # This program is distributed in the hope that it will be useful,
28 # but WITHOUT ANY WARRANTY; without even the implied warranty of
29 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 # GNU General Public License for more details.
32 # You should have received a copy of the GNU General Public License
33 # along with this program; if not, write to the Free Software Foundation,
34 # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
36 # ***** END GPL LICENCE BLOCK *****
37 # --------------------------------------------------------------------------
40 from Blender import *
41 import bpy
42 from Blender.Mathutils import TriangleArea
44 import Blender
45 import BPyMesh
46 dict2MeshWeight= BPyMesh.dict2MeshWeight
47 meshWeight2Dict= BPyMesh.meshWeight2Dict
49 def rem_free_verts(me):
50 vert_users= [0] * len(me.verts)
51 for f in me.faces:
52 for v in f:
53 vert_users[v.index]+=1
55 for e in me.edges:
56 for v in e: # loop on edge verts
57 vert_users[v.index]+=1
59 verts_free= [i for i, users in enumerate(vert_users) if not users]
61 if verts_free:
62 pass
63 me.verts.delete(verts_free)
64 return len(verts_free)
66 def rem_free_edges(me, limit=None):
67 ''' Only remove based on limit if a limit is set, else remove all '''
69 edgeDict= {} # will use a set when python 2.4 is standard.
71 for f in me.faces:
72 for edkey in f.edge_keys:
73 edgeDict[edkey] = None
75 edges_free= []
76 for e in me.edges:
77 if not edgeDict.has_key(e.key):
78 edges_free.append(e)
80 if limit != None:
81 edges_free= [e for e in edges_free if e.length <= limit]
83 me.edges.delete(edges_free)
84 return len(edges_free)
86 def rem_area_faces(me, limit=0.001):
87 ''' Faces that have an area below the limit '''
88 rem_faces= [f for f in me.faces if f.area <= limit]
89 if rem_faces:
90 me.faces.delete( 0, rem_faces )
91 return len(rem_faces)
93 def rem_perimeter_faces(me, limit=0.001):
94 ''' Faces whos combine edge length is below the limit '''
95 def faceEdLen(f):
96 v= f.v
97 if len(v) == 3:
98 return\
99 (v[0].co-v[1].co).length +\
100 (v[1].co-v[2].co).length +\
101 (v[2].co-v[0].co).length
102 else: # 4
103 return\
104 (v[0].co-v[1].co).length +\
105 (v[1].co-v[2].co).length +\
106 (v[2].co-v[3].co).length +\
107 (v[3].co-v[0].co).length
108 rem_faces= [f for f in me.faces if faceEdLen(f) <= limit]
109 if rem_faces:
110 me.faces.delete( 0, rem_faces )
111 return len(rem_faces)
113 def rem_unused_materials(me):
114 materials= me.materials
115 len_materials= len(materials)
116 if len_materials < 2:
117 return 0
119 rem_materials= 0
121 material_users= dict( [(i,0) for i in xrange(len_materials)] )
123 for f in me.faces:
124 # Make sure the face index isnt too big. this happens sometimes.
125 if f.mat >= len_materials:
126 f.mat=0
127 material_users[f.mat] += 1
129 mat_idx_subtract= 0
130 reindex_mapping= dict( [(i,0) for i in xrange(len_materials)] )
131 i= len_materials
132 while i:
133 i-=1
135 if material_users[i] == 0:
136 mat_idx_subtract+=1
137 reindex_mapping[i]= mat_idx_subtract
138 materials.pop(i)
139 rem_materials+=1
141 for f in me.faces:
142 f.mat= f.mat - reindex_mapping[f.mat]
144 me.materials= materials
145 return rem_materials
148 def rem_free_groups(me, groupNames, vWeightDict):
149 ''' cound how many vert users a group has and remove unused groups '''
150 rem_groups = 0
151 groupUserDict= dict([(group,0) for group in groupNames])
153 for vertexWeight in vWeightDict:
154 for group, weight in vertexWeight.iteritems():
155 groupUserDict[group] += 1
157 i=len(groupNames)
158 while i:
159 i-=1
160 group= groupNames[i]
161 if groupUserDict[group] == 0:
162 del groupNames[i]
163 print '\tremoving, vgroup', group
164 rem_groups+=1
165 return rem_groups
167 def rem_zero_weights(me, limit, groupNames, vWeightDict):
168 ''' remove verts from a group when their weight is zero.'''
169 rem_vweight_count= 0
170 for vertexWeight in vWeightDict:
171 items= vertexWeight.items()
172 for group, weight in items:
173 if weight < limit:
174 del vertexWeight[group]
175 rem_vweight_count+= 1
177 return rem_vweight_count
180 def normalize_vweight(me, groupNames, vWeightDict):
181 for vertexWeight in vWeightDict:
182 unit= 0.0
183 for group, weight in vertexWeight.iteritems():
184 unit+= weight
186 if unit != 1.0 and unit != 0.0:
187 for group, weight in vertexWeight.iteritems():
188 vertexWeight[group]= weight/unit
190 def isnan(f):
191 fstring = str(f).lower()
192 if 'nan' in fstring:
193 return True
194 if 'inf' in fstring:
195 return True
197 return False
199 def fix_nan_verts__internal(me):
200 rem_nan = 0
201 for v in me.verts:
202 co = v.co
203 for i in (0,1,2):
204 if isnan(co[i]):
205 co[i] = 0.0
206 rem_nan += 1
207 return rem_nan
209 def fix_nan_verts(me):
210 rem_nan = 0
211 key = me.key
212 if key:
213 # Find the object, and get a mesh thats thinked to the oblink.
214 # this is a bit crap but needed to set the active key.
215 me_oblink = None
216 for ob in bpy.data.objects:
217 me_oblink = ob.getData(mesh=1)
218 if me_oblink == me:
219 me = me_oblink
220 break
221 if not me_oblink:
222 ob = None
224 if key and ob:
225 blocks = key.blocks
226 # print blocks
227 orig_pin = ob.pinShape
228 orig_shape = ob.activeShape
229 orig_relative = key.relative
230 ob.pinShape = True
231 for i, block in enumerate(blocks):
232 ob.activeShape = i+1
233 ob.makeDisplayList()
234 rem_nan += fix_nan_verts__internal(me)
235 me.update(block.name) # get the new verts
236 ob.pinShape = orig_pin
237 ob.activeShape = orig_shape
238 key.relative = orig_relative
240 else: # No keys, simple operation
241 rem_nan = fix_nan_verts__internal(me)
243 return rem_nan
245 def fix_nan_uvs(me):
246 rem_nan = 0
247 if me.faceUV:
248 orig_uvlayer = me.activeUVLayer
249 for uvlayer in me.getUVLayerNames():
250 me.activeUVLayer = uvlayer
251 for f in me.faces:
252 for uv in f.uv:
253 for i in (0,1):
254 if isnan(uv[i]):
255 uv[i] = 0.0
256 rem_nan += 1
257 me.activeUVLayer = orig_uvlayer
258 return rem_nan
261 def has_vcol(me):
262 for f in me.faces:
263 for col in f.col:
264 if not (255 == col.r == col.g == col.b):
265 return True
266 return False
268 def rem_white_vcol_layers(me):
269 vcols_removed = 0
270 if me.vertexColors:
271 for col in me.getColorLayerNames():
272 me.activeColorLayer = col
273 if not has_vcol(me):
274 me.removeColorLayer(col)
275 vcols_removed += 1
277 return vcols_removed
280 def main():
281 sce= bpy.data.scenes.active
282 obsel= list(sce.objects.context)
283 actob= sce.objects.active
285 is_editmode= Window.EditMode()
287 # Edit mode object is not active, add it to the list.
288 if is_editmode and (not actob.sel):
289 obsel.append(actob)
292 #====================================#
293 # Popup menu to select the functions #
294 #====================================#
296 CLEAN_ALL_DATA= Draw.Create(0)
297 CLEAN_VERTS_FREE= Draw.Create(1)
298 CLEAN_EDGE_NOFACE= Draw.Create(0)
299 CLEAN_EDGE_SMALL= Draw.Create(0)
300 CLEAN_FACE_PERIMETER= Draw.Create(0)
301 CLEAN_FACE_SMALL= Draw.Create(0)
303 CLEAN_MATERIALS= Draw.Create(0)
304 CLEAN_WHITE_VCOL_LAYERS= Draw.Create(0)
305 CLEAN_GROUP= Draw.Create(0)
306 CLEAN_VWEIGHT= Draw.Create(0)
307 CLEAN_WEIGHT_NORMALIZE= Draw.Create(0)
308 limit= Draw.Create(0.01)
310 CLEAN_NAN_VERTS= Draw.Create(0)
311 CLEAN_NAN_UVS= Draw.Create(0)
313 # Get USER Options
315 pup_block= [\
316 ('Verts: free', CLEAN_VERTS_FREE, 'Remove verts that are not used by an edge or a face.'),\
317 ('Edges: free', CLEAN_EDGE_NOFACE, 'Remove edges that are not in a face.'),\
318 ('Edges: short', CLEAN_EDGE_SMALL, 'Remove edges that are below the length limit.'),\
319 ('Faces: small perimeter', CLEAN_FACE_PERIMETER, 'Remove faces below the perimeter limit.'),\
320 ('Faces: small area', CLEAN_FACE_SMALL, 'Remove faces below the area limit (may remove faces stopping T-face artifacts).'),\
321 ('limit: ', limit, 0.001, 1.0, 'Limit for the area and length tests above (a higher limit will remove more data).'),\
322 ('Material Clean', CLEAN_MATERIALS, 'Remove unused materials.'),\
323 ('Color Layers', CLEAN_WHITE_VCOL_LAYERS, 'Remove vertex color layers that are totaly white'),\
324 ('VGroup Clean', CLEAN_GROUP, 'Remove vertex groups that have no verts using them.'),\
325 ('Weight Clean', CLEAN_VWEIGHT, 'Remove zero weighted verts from groups (limit is zero threshold).'),\
326 ('WeightNormalize', CLEAN_WEIGHT_NORMALIZE, 'Make the sum total of vertex weights accross vgroups 1.0 for each vertex.'),\
327 'Clean NAN values',\
328 ('NAN Verts', CLEAN_NAN_VERTS, 'Make NAN or INF verts (0,0,0)'),\
329 ('NAN UVs', CLEAN_NAN_UVS, 'Make NAN or INF UVs (0,0)'),\
330 '',\
331 ('All Mesh Data', CLEAN_ALL_DATA, 'Warning! Operate on ALL mesh objects in your Blend file. Use with care'),\
334 if not Draw.PupBlock('Clean Selected Meshes...', pup_block):
335 return
337 CLEAN_VERTS_FREE= CLEAN_VERTS_FREE.val
338 CLEAN_EDGE_NOFACE= CLEAN_EDGE_NOFACE.val
339 CLEAN_EDGE_SMALL= CLEAN_EDGE_SMALL.val
340 CLEAN_FACE_PERIMETER= CLEAN_FACE_PERIMETER.val
341 CLEAN_FACE_SMALL= CLEAN_FACE_SMALL.val
342 CLEAN_MATERIALS= CLEAN_MATERIALS.val
343 CLEAN_WHITE_VCOL_LAYERS= CLEAN_WHITE_VCOL_LAYERS.val
344 CLEAN_GROUP= CLEAN_GROUP.val
345 CLEAN_VWEIGHT= CLEAN_VWEIGHT.val
346 CLEAN_WEIGHT_NORMALIZE= CLEAN_WEIGHT_NORMALIZE.val
347 limit= limit.val
348 CLEAN_ALL_DATA= CLEAN_ALL_DATA.val
349 CLEAN_NAN_VERTS= CLEAN_NAN_VERTS.val
350 CLEAN_NAN_UVS= CLEAN_NAN_UVS.val
352 if is_editmode: Window.EditMode(0)
354 if CLEAN_ALL_DATA:
355 if CLEAN_GROUP or CLEAN_VWEIGHT or CLEAN_WEIGHT_NORMALIZE:
356 # For groups we need the objects linked to the mesh
357 meshes= [ob.getData(mesh=1) for ob in bpy.data.objects if ob.type == 'Mesh' if not ob.lib]
358 else:
359 meshes= bpy.data.meshes
360 else:
361 meshes= [ob.getData(mesh=1) for ob in obsel if ob.type == 'Mesh']
363 tot_meshes = len(meshes) # so we can decrement libdata
364 rem_face_count= rem_edge_count= rem_vert_count= rem_material_count= rem_vcol_layer_count= rem_group_count= rem_vweight_count= fix_nan_vcount= fix_nan_uvcount= 0
365 if not meshes:
366 if is_editmode: Window.EditMode(1)
367 Draw.PupMenu('No meshes to clean')
369 Blender.Window.WaitCursor(1)
370 bpy.data.meshes.tag = False
371 for me in meshes:
373 # Dont touch the same data twice
374 if me.tag:
375 tot_meshes -= 1
376 continue
377 me.tag = True
379 if me.lib:
380 tot_meshes -= 1
381 continue
383 if me.multires:
384 multires_level_orig = me.multiresDrawLevel
385 me.multiresDrawLevel = 1
386 print 'Warning, cannot perform destructive operations on multires mesh:', me.name
387 else:
388 if CLEAN_FACE_SMALL:
389 rem_face_count += rem_area_faces(me, limit)
391 if CLEAN_FACE_PERIMETER:
392 rem_face_count += rem_perimeter_faces(me, limit)
394 if CLEAN_EDGE_SMALL: # for all use 2- remove all edges.
395 rem_edge_count += rem_free_edges(me, limit)
397 if CLEAN_EDGE_NOFACE:
398 rem_edge_count += rem_free_edges(me)
400 if CLEAN_VERTS_FREE:
401 rem_vert_count += rem_free_verts(me)
403 if CLEAN_MATERIALS:
404 rem_material_count += rem_unused_materials(me)
406 if CLEAN_WHITE_VCOL_LAYERS:
407 rem_vcol_layer_count += rem_white_vcol_layers(me)
409 if CLEAN_VWEIGHT or CLEAN_GROUP or CLEAN_WEIGHT_NORMALIZE:
410 groupNames, vWeightDict= meshWeight2Dict(me)
412 if CLEAN_VWEIGHT:
413 rem_vweight_count += rem_zero_weights(me, limit, groupNames, vWeightDict)
415 if CLEAN_GROUP:
416 rem_group_count += rem_free_groups(me, groupNames, vWeightDict)
417 pass
419 if CLEAN_WEIGHT_NORMALIZE:
420 normalize_vweight(me, groupNames, vWeightDict)
422 # Copy back to mesh vertex groups.
423 dict2MeshWeight(me, groupNames, vWeightDict)
425 if CLEAN_NAN_VERTS:
426 fix_nan_vcount = fix_nan_verts(me)
428 if CLEAN_NAN_UVS:
429 fix_nan_uvcount = fix_nan_uvs(me)
431 # restore multires.
432 if me.multires:
433 me.multiresDrawLevel = multires_level_orig
435 Blender.Window.WaitCursor(0)
436 if is_editmode: Window.EditMode(0)
437 stat_string= 'Removed from ' + str(tot_meshes) + ' Mesh(es)%t|'
439 if CLEAN_VERTS_FREE: stat_string+= 'Verts: %i|' % rem_vert_count
440 if CLEAN_EDGE_SMALL or CLEAN_EDGE_NOFACE: stat_string+= 'Edges: %i|' % rem_edge_count
441 if CLEAN_FACE_SMALL or CLEAN_FACE_PERIMETER: stat_string+= 'Faces: %i|' % rem_face_count
442 if CLEAN_MATERIALS: stat_string+= 'Materials: %i|' % rem_material_count
443 if CLEAN_WHITE_VCOL_LAYERS: stat_string+= 'Color Layers: %i|' % rem_vcol_layer_count
444 if CLEAN_VWEIGHT: stat_string+= 'VWeights: %i|' % rem_vweight_count
445 if CLEAN_GROUP: stat_string+= 'VGroups: %i|' % rem_group_count
446 if CLEAN_NAN_VERTS: stat_string+= 'Vert Nan Fix: %i|' % fix_nan_vcount
447 if CLEAN_NAN_UVS: stat_string+= 'UV Nan Fix: %i|' % fix_nan_uvcount
448 Draw.PupMenu(stat_string)
451 if __name__ == '__main__':
452 main()