added list2MeshWeight and meshWeight2List - faster then the dict equivilents and...
[blender.git] / release / scripts / mesh_cleanup.py
1 #!BPY
2 """
3 Name: 'Clean meshes'
4 Blender: 228
5 Group: 'Mesh'
6 Tooltip: 'Clean unused data from all selected mesh objects.'
7 """
8 from Blender import *
9 from Blender.Mathutils import TriangleArea
10
11 import Blender
12 import BPyMesh
13 dict2MeshWeight= BPyMesh.dict2MeshWeight
14 meshWeight2Dict= BPyMesh.meshWeight2Dict
15
16 def rem_free_verts(me):
17         vert_users= [0] * len(me.verts)
18         for f in me.faces:
19                 for v in f.v:
20                         vert_users[v.index]+=1
21         
22         for e in me.edges:
23                 for v in e: # loop on edge verts
24                         vert_users[v.index]+=1
25         
26         verts_free= []
27         for i, users in enumerate(vert_users):
28                 if not users:
29                         verts_free.append(i)
30         
31         if verts_free:
32                 pass
33                 me.verts.delete(verts_free)
34         return len(verts_free)
35         
36 def rem_free_edges(me, limit=None):
37         ''' Only remove based on limit if a limit is set, else remove all '''
38         def sortPair(a,b):
39                 return min(a,b), max(a,b)
40         
41         edgeDict= {} # will use a set when python 2.4 is standard.
42         
43         for f in me.faces:
44                 v= f.v
45                 for i in xrange(len(v)):
46                         edgeDict[sortPair(v[i].index, v[i-1].index)]= None
47         
48         edges_free= []
49         for e in me.edges:
50                 if not edgeDict.has_key(sortPair(e.v1.index, e.v2.index)):
51                         edges_free.append(e)
52         
53         if limit != None:
54                 edges_free= [e for e in edges_free if (e.v1.co-e.v2.co).length <= limit]
55         
56         me.edges.delete(edges_free)
57         return len(edges_free)
58
59 def rem_area_faces(me, limit=0.001):
60         ''' Faces that have an area below the limit '''
61         rem_faces= [f for f in me.faces if f.area <= limit]
62         if rem_faces:
63                 me.faces.delete( 0, rem_faces )
64         return len(rem_faces)
65
66 def rem_perimeter_faces(me, limit=0.001):
67         ''' Faces whos combine edge length is below the limit '''
68         def faceEdLen(f):
69                 v= f.v
70                 if len(v) == 3:
71                         return\
72                         (v[0].co-v[1].co).length +\
73                         (v[1].co-v[2].co).length +\
74                         (v[2].co-v[0].co).length
75                 else: # 4
76                         return\
77                         (v[0].co-v[1].co).length +\
78                         (v[1].co-v[2].co).length +\
79                         (v[2].co-v[3].co).length +\
80                         (v[3].co-v[0].co).length
81         rem_faces= [f for f in me.faces if faceEdLen(f) <= limit]
82         if rem_faces:
83                 me.faces.delete( 0, rem_faces )
84         return len(rem_faces)
85
86 def rem_unused_materials(me):
87         materials= me.materials
88         len_materials= len(materials)
89         if len_materials < 2:
90                 return 0
91                 
92         rem_materials= 0
93         
94         material_users= dict( [(i,0) for i in xrange(len_materials)] )
95         
96         for f in me.faces:
97                 # Make sure the face index isnt too big. this happens sometimes.
98                 if f.mat >= len_materials:
99                         f.mat=0
100                 material_users[f.mat] += 1
101         
102         mat_idx_subtract= 0
103         reindex_mapping= dict( [(i,0) for i in xrange(len_materials)] )
104         i= len_materials
105         while i:
106                 i-=1
107                 
108                 if material_users[i] == 0:
109                         mat_idx_subtract+=1
110                         reindex_mapping[i]= mat_idx_subtract
111                         materials.pop(i)
112                         rem_materials+=1
113         
114         for f in me.faces:
115                 f.mat= f.mat - reindex_mapping[f.mat]
116         
117         me.materials= materials
118         return rem_materials
119
120
121 def rem_free_groups(me, groupNames, vWeightDict):
122         ''' cound how many vert users a group has and remove unsued groups '''
123         rem_groups              = 0
124         groupUserDict= dict([(group,0) for group in groupNames])
125         
126         for vertexWeight in vWeightDict:
127                 for group, weight in vertexWeight.iteritems():
128                         groupUserDict[group] += 1
129         
130         i=len(groupNames)
131         while i:
132                 i-=1
133                 group= groupNames[i]
134                 if groupUserDict[group] == 0:
135                         del groupNames[i]
136                         print '\tremoving, vgroup', group
137                         rem_groups+=1
138         return rem_groups
139
140 def rem_zero_weights(me, limit, groupNames, vWeightDict):
141         ''' remove verts from a group when their weight is zero.'''
142         rem_vweight_count= 0
143         for vertexWeight in vWeightDict:
144                 items= vertexWeight.items()
145                 for group, weight in items:
146                         if weight < limit:
147                                 del vertexWeight[group]
148                                 rem_vweight_count+= 1
149
150         return rem_vweight_count
151
152         
153 def normalize_vweight(me, groupNames, vWeightDict):
154         for vertexWeight in vWeightDict:
155                 unit= 0.0
156                 for group, weight in vertexWeight.iteritems():
157                         unit+= weight
158                 
159                 if unit != 1.0 and unit != 0.0:
160                         for group, weight in vertexWeight.iteritems():
161                                 vertexWeight[group]= weight/unit
162
163
164
165 def main():     
166         scn= Scene.GetCurrent()
167         obsel= Object.GetSelected()
168         actob= scn.getActiveObject()
169         
170         is_editmode= Window.EditMode()
171         
172         # Edit mode object is not active, add it to the list.
173         if is_editmode and (not actob.sel):
174                 obsel.append(actob)
175         
176         
177         #====================================#
178         # Popup menu to select the functions #
179         #====================================#
180         
181         CLEAN_ALL_DATA= Draw.Create(0)
182         CLEAN_VERTS_FREE= Draw.Create(1)
183         CLEAN_EDGE_NOFACE= Draw.Create(0)
184         CLEAN_EDGE_SMALL= Draw.Create(0)
185         CLEAN_FACE_PERIMETER= Draw.Create(0)
186         CLEAN_FACE_SMALL= Draw.Create(0)
187         
188         CLEAN_MATERIALS= Draw.Create(0)
189         CLEAN_GROUP= Draw.Create(0)
190         CLEAN_VWEIGHT= Draw.Create(0)
191         CLEAN_WEIGHT_NORMALIZE= Draw.Create(0)
192         limit= Draw.Create(0.01)
193         # Get USER Options
194         
195         pup_block= [\
196         ('Verts: free', CLEAN_VERTS_FREE, 'Remove verts that are not used by an edge or a face.'),\
197         ('Edges: free', CLEAN_EDGE_NOFACE, 'Remove edges that are not in a face.'),\
198         ('Edges: short', CLEAN_EDGE_SMALL, 'Remove edges that are below the length limit.'),\
199         ('Faces: small perimeter', CLEAN_FACE_PERIMETER, 'Remove faces below the perimeter limit.'),\
200         ('Faces: small area', CLEAN_FACE_SMALL, 'Remove faces below the area limit (may remove faces stopping T-face artifacts).'),\
201         'Materials',\
202         ('Material Clean', CLEAN_MATERIALS, 'Remove unused materials.'),\
203         'VGroups',\
204         ('Group Clean', CLEAN_GROUP, 'Remove vertex groups that have no verts using them.'),\
205         ('Weight Clean', CLEAN_VWEIGHT, 'Remove zero weighted verts from groups (limit is zero threshold).'),\
206         ('Weight Normalize', CLEAN_WEIGHT_NORMALIZE, 'Make the sum total of vertex weights accross vgroups 1.0 for each vertex.'),\
207         '',\
208         ('limit: ', limit, 0.001, 1.0, 'Limit used for the area and length tests above (a higher limit will remove more data).'),\
209         '',\
210         ('All Mesh Data', CLEAN_ALL_DATA, 'Warning! Operate on ALL mesh objects in your Blend file. Use with care'),\
211         ]
212         
213         if not Draw.PupBlock('Clean Selected Meshes...', pup_block):
214                 return
215         
216         CLEAN_VERTS_FREE= CLEAN_VERTS_FREE.val
217         CLEAN_EDGE_NOFACE= CLEAN_EDGE_NOFACE.val
218         CLEAN_EDGE_SMALL= CLEAN_EDGE_SMALL.val
219         CLEAN_FACE_PERIMETER= CLEAN_FACE_PERIMETER.val
220         CLEAN_FACE_SMALL= CLEAN_FACE_SMALL.val
221         CLEAN_MATERIALS= CLEAN_MATERIALS.val
222         CLEAN_GROUP= CLEAN_GROUP.val
223         CLEAN_VWEIGHT= CLEAN_VWEIGHT.val
224         CLEAN_WEIGHT_NORMALIZE= CLEAN_WEIGHT_NORMALIZE.val
225         limit= limit.val
226         CLEAN_ALL_DATA= CLEAN_ALL_DATA.val
227         
228         if is_editmode: Window.EditMode(0)
229         
230         if CLEAN_ALL_DATA:
231                 if CLEAN_GROUP or CLEAN_VWEIGHT or CLEAN_WEIGHT_NORMALIZE:
232                         # For groups we need the objects linked to the mesh
233                         meshes= [ob.getData(mesh=1) for ob in Object.Get() if ob.getType() == 'Mesh']
234                 else:
235                         meshes= Mesh.Get()
236         else:
237                 meshes= [ob.getData(mesh=1) for ob in obsel if ob.getType() == 'Mesh']
238         
239         rem_face_count= rem_edge_count= rem_vert_count= rem_material_count= rem_group_count= rem_vweight_count= 0
240         
241         for me in meshes:
242                 if CLEAN_FACE_SMALL:
243                         rem_face_count += rem_area_faces(me, limit)
244                         
245                 if CLEAN_FACE_PERIMETER:
246                         rem_face_count += rem_perimeter_faces(me, limit)
247                 
248                 if CLEAN_EDGE_SMALL: # for all use 2- remove all edges.
249                         rem_edge_count += rem_free_edges(me, limit)
250                 
251                 if CLEAN_EDGE_NOFACE:
252                         rem_edge_count += rem_free_edges(me)
253                 
254                 if CLEAN_VERTS_FREE:
255                         rem_vert_count += rem_free_verts(me)
256                 
257                 if CLEAN_MATERIALS:
258                         rem_material_count += rem_unused_materials(me)
259                 
260                 if CLEAN_VWEIGHT or CLEAN_GROUP or CLEAN_WEIGHT_NORMALIZE:
261                         groupNames, vWeightDict= meshWeight2Dict(me)
262                         
263                         if CLEAN_VWEIGHT:
264                                 rem_vweight_count += rem_zero_weights(me, limit, groupNames, vWeightDict)
265                         
266                         if CLEAN_GROUP:
267                                 rem_group_count += rem_free_groups(me, groupNames, vWeightDict)
268                                 pass
269                         
270                         if CLEAN_WEIGHT_NORMALIZE:
271                                 normalize_vweight(me, groupNames, vWeightDict)
272                         
273                         # Copy back to mesh vertex groups.
274                         dict2MeshWeight(me, groupNames, vWeightDict)
275                         
276                 
277         if is_editmode: Window.EditMode(0)
278         stat_string= 'Removed from ' + str(len(meshes)) + ' Mesh(es)%t|'
279         
280         if CLEAN_VERTS_FREE:                                                    stat_string+= 'Verts: %i|' % rem_edge_count
281         if CLEAN_EDGE_SMALL or CLEAN_EDGE_NOFACE:               stat_string+= 'Edges: %i|' % rem_edge_count
282         if CLEAN_FACE_SMALL or CLEAN_FACE_PERIMETER:    stat_string+= 'Faces: %i|' % rem_face_count
283         if CLEAN_MATERIALS:                                                             stat_string+= 'Materials: %i|' % rem_material_count
284         if CLEAN_VWEIGHT:                                                               stat_string+= 'VWeights: %i|' % rem_vweight_count
285         if CLEAN_GROUP:                                                                 stat_string+= 'VGroups: %i|' % rem_group_count
286         
287         Draw.PupMenu(stat_string)
288         
289         
290 if __name__ == '__main__':
291         main()