6 Tooltip: 'Clean unused data from all selected mesh objects.'
9 __author__
= ["Campbell Barton"]
10 __url__
= ("blender", "elysiun", "http://members.iinet.net.au/~cpbarton/ideasman/")
15 Cleans unused data from selected meshes
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 # --------------------------------------------------------------------------
42 from Blender
.Mathutils
import TriangleArea
46 dict2MeshWeight
= BPyMesh
.dict2MeshWeight
47 meshWeight2Dict
= BPyMesh
.meshWeight2Dict
49 def rem_free_verts(me
):
50 vert_users
= [0] * len(me
.verts
)
53 vert_users
[v
.index
]+=1
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
]
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.
72 for edkey
in f
.edge_keys
:
73 edgeDict
[edkey
] = None
77 if not edgeDict
.has_key(e
.key
):
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
]
90 me
.faces
.delete( 0, rem_faces
)
93 def rem_perimeter_faces(me
, limit
=0.001):
94 ''' Faces whos combine edge length is below the limit '''
99 (v
[0].co
-v
[1].co
).length
+\
100 (v
[1].co
-v
[2].co
).length
+\
101 (v
[2].co
-v
[0].co
).length
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
]
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:
121 material_users
= dict( [(i
,0) for i
in xrange(len_materials
)] )
124 # Make sure the face index isnt too big. this happens sometimes.
125 if f
.mat
>= len_materials
:
127 material_users
[f
.mat
] += 1
130 reindex_mapping
= dict( [(i
,0) for i
in xrange(len_materials
)] )
135 if material_users
[i
] == 0:
137 reindex_mapping
[i
]= mat_idx_subtract
142 f
.mat
= f
.mat
- reindex_mapping
[f
.mat
]
144 me
.materials
= materials
148 def rem_free_groups(me
, groupNames
, vWeightDict
):
149 ''' cound how many vert users a group has and remove unused groups '''
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
161 if groupUserDict
[group
] == 0:
163 print '\tremoving, vgroup', group
167 def rem_zero_weights(me
, limit
, groupNames
, vWeightDict
):
168 ''' remove verts from a group when their weight is zero.'''
170 for vertexWeight
in vWeightDict
:
171 items
= vertexWeight
.items()
172 for group
, weight
in items
:
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
:
183 for group
, weight
in vertexWeight
.iteritems():
186 if unit
!= 1.0 and unit
!= 0.0:
187 for group
, weight
in vertexWeight
.iteritems():
188 vertexWeight
[group
]= weight
/unit
191 fstring
= str(f
).lower()
199 def fix_nan_verts__internal(me
):
209 def fix_nan_verts(me
):
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.
216 for ob
in bpy
.data
.objects
:
217 me_oblink
= ob
.getData(mesh
=1)
227 orig_pin
= ob
.pinShape
228 orig_shape
= ob
.activeShape
229 orig_relative
= key
.relative
231 for i
, block
in enumerate(blocks
):
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
)
248 orig_uvlayer
= me
.activeUVLayer
249 for uvlayer
in me
.getUVLayerNames():
250 me
.activeUVLayer
= uvlayer
257 me
.activeUVLayer
= orig_uvlayer
264 if not (255 == col
.r
== col
.g
== col
.b
):
268 def rem_white_vcol_layers(me
):
271 for col
in me
.getColorLayerNames():
272 me
.activeColorLayer
= col
274 me
.removeColorLayer(col
)
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
):
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)
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.'),\
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)'),\
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
):
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
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)
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
]
359 meshes
= bpy
.data
.meshes
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
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
373 # Dont touch the same data twice
384 multires_level_orig
= me
.multiresDrawLevel
385 me
.multiresDrawLevel
= 1
386 print 'Warning, cannot perform destructive operations on multires mesh:', me
.name
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
)
401 rem_vert_count
+= rem_free_verts(me
)
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
)
413 rem_vweight_count
+= rem_zero_weights(me
, limit
, groupNames
, vWeightDict
)
416 rem_group_count
+= rem_free_groups(me
, groupNames
, vWeightDict
)
419 if CLEAN_WEIGHT_NORMALIZE
:
420 normalize_vweight(me
, groupNames
, vWeightDict
)
422 # Copy back to mesh vertex groups.
423 dict2MeshWeight(me
, groupNames
, vWeightDict
)
426 fix_nan_vcount
= fix_nan_verts(me
)
429 fix_nan_uvcount
= fix_nan_uvs(me
)
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__':