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