Welcome to mirror list, hosted at ThFree Co, Russian Federation.

mesh_cleanup.py « scripts « release - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 3d4caa50b1d832ab4205250cacfa5579d4549bba (plain)
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#!BPY
"""
Name: 'Clean meshes'
Blender: 228
Group: 'Mesh'
Tooltip: 'Clean unused data from all selected mesh objects.'
"""
from Blender import *
from Blender.Mathutils import TriangleArea

import Blender
import BPyMesh
reload(BPyMesh)
dict2MeshWeight= BPyMesh.dict2MeshWeight
meshWeight2Dict= BPyMesh.meshWeight2Dict

def rem_free_verts(me):
	vert_users= [0] * len(me.verts)
	for f in me.faces:
		for v in f.v:
			vert_users[v.index]+=1
	
	for e in me.edges:
		for v in e: # loop on edge verts
			vert_users[v.index]+=1
	
	verts_free= []
	for i, users in enumerate(vert_users):
		if not users:
			verts_free.append(i)
	
	if verts_free:
		pass
		me.verts.delete(verts_free)
	return len(verts_free)
	
def rem_free_edges(me, limit=None):
	''' Only remove based on limit if a limit is set, else remove all '''
	def sortPair(a,b):
		return min(a,b), max(a,b)
	
	edgeDict= {} # will use a set when python 2.4 is standard.
	
	for f in me.faces:
		v= f.v
		for i in xrange(len(v)):
			edgeDict[sortPair(v[i].index, v[i-1].index)]= None
	
	edges_free= []
	for e in me.edges:
		if not edgeDict.has_key(sortPair(e.v1.index, e.v2.index)):
			edges_free.append(e)
	
	if limit != None:
		edges_free= [e for e in edges_free if (e.v1.co-e.v2.co).length <= limit]
	
	me.edges.delete(edges_free)
	return len(edges_free)

def rem_area_faces(me, limit=0.001):
	''' Faces that have an area below the limit '''
	rem_faces= [f for f in me.faces if f.area <= limit]
	if rem_faces:
		me.faces.delete( 0, rem_faces )
	return len(rem_faces)

def rem_perimeter_faces(me, limit=0.001):
	''' Faces whos combine edge length is below the limit '''
	def faceEdLen(f):
		v= f.v
		if len(v) == 3:
			return\
			(v[0].co-v[1].co).length +\
			(v[1].co-v[2].co).length +\
			(v[2].co-v[0].co).length
		else: # 4
			return\
			(v[0].co-v[1].co).length +\
			(v[1].co-v[2].co).length +\
			(v[2].co-v[3].co).length +\
			(v[3].co-v[0].co).length
	rem_faces= [f for f in me.faces if faceEdLen(f) <= limit]
	if rem_faces:
		me.faces.delete( 0, rem_faces )
	return len(rem_faces)

def rem_unused_materials(me):
	materials= me.materials
	len_materials= len(materials)
	if len_materials < 2:
		return 0
		
	rem_materials= 0
	
	material_users= dict( [(i,0) for i in xrange(len_materials)] )
	
	for f in me.faces:
		# Make sure the face index isnt too big. this happens sometimes.
		if f.mat >= len_materials:
			f.mat=0
		material_users[f.mat] += 1
	
	mat_idx_subtract= 0
	reindex_mapping= dict( [(i,0) for i in xrange(len_materials)] )
	i= len_materials
	while i:
		i-=1
		
		if material_users[i] == 0:
			mat_idx_subtract+=1
			reindex_mapping[i]= mat_idx_subtract
			materials.pop(i)
			rem_materials+=1
	
	for f in me.faces:
		f.mat= f.mat - reindex_mapping[f.mat]
	
	me.materials= materials
	return rem_materials


def rem_free_groups(me, groupNames, vWeightDict):
	''' cound how many vert users a group has and remove unsued groups '''
	rem_groups		= 0
	groupUserDict= dict([(group,0) for group in groupNames])
	
	for vertexWeight in vWeightDict:
		for group, weight in vertexWeight.iteritems():
			groupUserDict[group] += 1
	
	i=len(groupNames)
	while i:
		i-=1
		group= groupNames[i]
		if groupUserDict[group] == 0:
			del groupNames[i]
			print '\tremoving, vgroup', group
			rem_groups+=1
	return rem_groups

def rem_zero_weights(me, limit, groupNames, vWeightDict):
	''' remove verts from a group when their weight is zero.'''
	rem_vweight_count= 0
	for vertexWeight in vWeightDict:
		items= vertexWeight.items()
		for group, weight in items:
			if weight < limit:
				del vertexWeight[group]
				rem_vweight_count+= 1

	return rem_vweight_count

	
def normalize_vweight(me, groupNames, vWeightDict):
	for vertexWeight in vWeightDict:
		unit= 0.0
		for group, weight in vertexWeight.iteritems():
			unit+= weight
		
		if unit != 1.0 and unit != 0.0:
			for group, weight in vertexWeight.iteritems():
				vertexWeight[group]= weight/unit



def main():	
	scn= Scene.GetCurrent()
	obsel= Object.GetSelected()
	actob= scn.getActiveObject()
	
	is_editmode= Window.EditMode()
	
	# Edit mode object is not active, add it to the list.
	if is_editmode and (not actob.sel):
		obsel.append(actob)
	
	
	#====================================#
	# Popup menu to select the functions #
	#====================================#
	
	CLEAN_ALL_DATA= Draw.Create(0)
	CLEAN_VERTS_FREE= Draw.Create(1)
	CLEAN_EDGE_NOFACE= Draw.Create(0)
	CLEAN_EDGE_SMALL= Draw.Create(0)
	CLEAN_FACE_PERIMETER= Draw.Create(0)
	CLEAN_FACE_SMALL= Draw.Create(0)
	
	CLEAN_MATERIALS= Draw.Create(0)
	CLEAN_GROUP= Draw.Create(0)
	CLEAN_VWEIGHT= Draw.Create(0)
	CLEAN_WEIGHT_NORMALIZE= Draw.Create(0)
	limit= Draw.Create(0.01)
	# Get USER Options
	
	pup_block= [\
	('Verts: free', CLEAN_VERTS_FREE, 'Remove verts that are not used by an edge or a face.'),\
	('Edges: free', CLEAN_EDGE_NOFACE, 'Remove edges that are not in a face.'),\
	('Edges: short', CLEAN_EDGE_SMALL, 'Remove edges that are below the length limit.'),\
	('Faces: small perimeter', CLEAN_FACE_PERIMETER, 'Remove faces below the perimeter limit.'),\
	('Faces: small area', CLEAN_FACE_SMALL, 'Remove faces below the area limit (may remove faces stopping T-face artifacts).'),\
	'Materials',\
	('Material Clean', CLEAN_MATERIALS, 'Remove unused materials.'),\
	'VGroups',\
	('Group Clean', CLEAN_GROUP, 'Remove vertex groups that have no verts using them.'),\
	('Weight Clean', CLEAN_VWEIGHT, 'Remove zero weighted verts from groups (limit is zero threshold).'),\
	('Weight Normalize', CLEAN_WEIGHT_NORMALIZE, 'Make the sum total of vertex weights accross vgroups 1.0 for each vertex.'),\
	'',\
	('limit: ', limit, 0.001, 1.0, 'Limit used for the area and length tests above (a higher limit will remove more data).'),\
	'',\
	('All Mesh Data', CLEAN_ALL_DATA, 'Warning! Operate on ALL mesh objects in your Blend file. Use with care'),\
	]
	
	if not Draw.PupBlock('Clean Selected Meshes...', pup_block):
		return
	
	CLEAN_VERTS_FREE= CLEAN_VERTS_FREE.val
	CLEAN_EDGE_NOFACE= CLEAN_EDGE_NOFACE.val
	CLEAN_EDGE_SMALL= CLEAN_EDGE_SMALL.val
	CLEAN_FACE_PERIMETER= CLEAN_FACE_PERIMETER.val
	CLEAN_FACE_SMALL= CLEAN_FACE_SMALL.val
	CLEAN_MATERIALS= CLEAN_MATERIALS.val
	CLEAN_GROUP= CLEAN_GROUP.val
	CLEAN_VWEIGHT= CLEAN_VWEIGHT.val
	CLEAN_WEIGHT_NORMALIZE= CLEAN_WEIGHT_NORMALIZE.val
	limit= limit.val
	CLEAN_ALL_DATA= CLEAN_ALL_DATA.val
	
	if is_editmode: Window.EditMode(0)
	
	if CLEAN_ALL_DATA:
		if CLEAN_GROUP or CLEAN_VWEIGHT or CLEAN_WEIGHT_NORMALIZE:
			# For groups we need the objects linked to the mesh
			meshes= [ob.getData(mesh=1) for ob in Object.Get() if ob.getType() == 'Mesh']
		else:
			meshes= Mesh.Get()
	else:
		meshes= [ob.getData(mesh=1) for ob in obsel if ob.getType() == 'Mesh']
	
	rem_face_count= rem_edge_count= rem_vert_count= rem_material_count= rem_group_count= rem_vweight_count= 0
	
	for me in meshes:
		if CLEAN_FACE_SMALL:
			rem_face_count += rem_area_faces(me, limit)
			
		if CLEAN_FACE_PERIMETER:
			rem_face_count += rem_perimeter_faces(me, limit)
		
		if CLEAN_EDGE_SMALL: # for all use 2- remove all edges.
			rem_edge_count += rem_free_edges(me, limit)
		
		if CLEAN_EDGE_NOFACE:
			rem_edge_count += rem_free_edges(me)
		
		if CLEAN_VERTS_FREE:
			rem_vert_count += rem_free_verts(me)
		
		if CLEAN_MATERIALS:
			rem_material_count += rem_unused_materials(me)
		
		if CLEAN_VWEIGHT or CLEAN_GROUP or CLEAN_WEIGHT_NORMALIZE:
			groupNames, vWeightDict= meshWeight2Dict(me)
			
			if CLEAN_VWEIGHT:
				rem_vweight_count += rem_zero_weights(me, limit, groupNames, vWeightDict)
			
			if CLEAN_GROUP:
				rem_group_count += rem_free_groups(me, groupNames, vWeightDict)
				pass
			
			if CLEAN_WEIGHT_NORMALIZE:
				normalize_vweight(me, groupNames, vWeightDict)
			
			# Copy back to mesh vertex groups.
			dict2MeshWeight(me, groupNames, vWeightDict)
			
		
	if is_editmode: Window.EditMode(0)
	stat_string= 'Removed from ' + str(len(meshes)) + ' Mesh(es)%t|'
	
	if CLEAN_VERTS_FREE:							stat_string+= 'Verts: %i|' % rem_edge_count
	if CLEAN_EDGE_SMALL or CLEAN_EDGE_NOFACE:		stat_string+= 'Edges: %i|' % rem_edge_count
	if CLEAN_FACE_SMALL or CLEAN_FACE_PERIMETER:	stat_string+= 'Faces: %i|' % rem_face_count
	if CLEAN_MATERIALS:								stat_string+= 'Materials: %i|' % rem_material_count
	if CLEAN_VWEIGHT:								stat_string+= 'VWeights: %i|' % rem_vweight_count
	if CLEAN_GROUP:									stat_string+= 'VGroups: %i|' % rem_group_count
	
	Draw.PupMenu(stat_string)
	
	
if __name__ == '__main__':
	main()