Fixed some evil bugs in the poly reducer messing up UV's every now and then.
[blender.git] / release / scripts / bpymodules / BPyMesh.py
1 import Blender
2 import BPyMesh_redux
3 reload(BPyMesh_redux)
4
5 def redux(ob, REDUX=0.5, BOUNDRY_WEIGHT=5.0, FACE_AREA_WEIGHT=1.0, FACE_TRIANGULATE=True, DO_UV=True, DO_VCOL=True, DO_WEIGHTS=True):
6         if REDUX<0 or REDUX>1.0:
7                 raise 'Error, factor must be between 0 and 1.0'
8         BPyMesh_redux.redux(ob, REDUX, BOUNDRY_WEIGHT, FACE_AREA_WEIGHT, FACE_TRIANGULATE, DO_UV, DO_VCOL, DO_WEIGHTS)
9         
10 def meshWeight2Dict(me):
11         ''' Takes a mesh and return its group names and a list of dicts, one dict per vertex.
12         using the group as a key and a float value for the weight.
13         These 2 lists can be modified and then used with dict2MeshWeight to apply the changes.
14         '''
15         
16         vWeightDict= [dict() for i in xrange(len(me.verts))] # Sync with vertlist.
17         
18         # Clear the vert group.
19         groupNames= me.getVertGroupNames()
20         
21         for group in groupNames:
22                 for index, weight in me.getVertsFromGroup(group, 1): # (i,w)  tuples.
23                         vWeightDict[index][group]= weight
24         
25         # removed this because me may be copying teh vertex groups.
26         #for group in groupNames:
27         #       me.removeVertGroup(group)
28         
29         return groupNames, vWeightDict
30
31
32 def dict2MeshWeight(me, groupNames, vWeightDict):
33         ''' Takes a list of groups and a list of vertex Weight dicts as created by meshWeight2Dict
34         and applys it to the mesh.'''
35         
36         if len(vWeightDict) != len(me.verts):
37                 raise 'Error, Lists Differ in size, do not modify your mesh.verts before updating the weights'
38         
39         # Clear the vert group.
40         currentGroupNames= me.getVertGroupNames()
41         for group in currentGroupNames:
42                 if group not in groupNames:
43                         me.removeVertGroup(group) # messes up the active group.
44                 else:
45                         me.removeVertsFromGroup(group)
46         
47         # Add clean unused vert groupNames back
48         currentGroupNames= me.getVertGroupNames()
49         for group in groupNames:
50                 if group not in currentGroupNames:
51                         me.addVertGroup(group)
52         
53         add_ = Blender.Mesh.AssignModes.ADD
54         
55         vertList= [None]
56         for i, v in enumerate(me.verts):
57                 vertList[0]= i
58                 for group, weight in vWeightDict[i].iteritems():
59                         try:
60                                 me.assignVertsToGroup(group, vertList, min(1, max(0, weight)), add_)
61                         except:
62                                 pass # vert group is not used anymore.
63         
64         me.update()
65
66 def dictWeightMerge(dict_weights):
67         '''
68         Takes dict weight list and merges into 1 weight dict item and returns it
69         '''
70         
71         if not dict_weights:
72                 return {}
73         
74         keys= []
75         for weight in dict_weights:
76                 keys.extend([ (k, 0.0) for k in weight.iterkeys() ])
77         
78         new_wdict = dict(keys)
79         
80         len_dict_weights= len(dict_weights)
81         
82         for weight in dict_weights:
83                 for group, value in weight.iteritems():
84                         new_wdict[group] += value/len_dict_weights
85         
86         return new_wdict
87
88
89 FLIPNAMES=[\
90 ('Left','Right'),\
91 ('_L','_R'),\
92 ('-L','-R'),\
93 ('.L','.R'),\
94 ]
95
96 def dictWeightFlipGroups(dict_weight, groupNames, createNewGroups):
97         '''
98         Returns a weight with flip names
99         dict_weight - 1 vert weight.
100         groupNames - because we may need to add new group names.
101         dict_weight - Weather to make new groups where needed.
102         '''
103         
104         def flipName(name):
105                 for n1,n2 in FLIPNAMES:
106                         for nA, nB in ( (n1,n2), (n1.lower(),n2.lower()), (n1.upper(),n2.upper()) ):
107                                 if createNewGroups:
108                                         newName= name.replace(nA,nB)
109                                         if newName!=name:
110                                                 if newName not in groupNames:
111                                                         groupNames.append(newName)
112                                                 return newName
113                                         
114                                         newName= name.replace(nB,nA)
115                                         if newName!=name:
116                                                 if newName not in groupNames:
117                                                         groupNames.append(newName)
118                                                 return newName
119                                 
120                                 else:
121                                         newName= name.replace(nA,nB)
122                                         if newName!=name and newName in groupNames:
123                                                 return newName
124                                         
125                                         newName= name.replace(nB,nA)
126                                         if newName!=name and newName in groupNames:
127                                                 return newName
128                 
129                 return name
130                 
131         if not dict_weight:
132                 return dict_weight, groupNames
133         
134         
135         new_wdict = {}
136         for group, weight in dict_weight.iteritems():
137                 flipname= flipName(group)
138                 new_wdict[flipname]= weight
139         
140         return new_wdict, groupNames
141         
142
143 def getMeshFromObject(ob, container_mesh=None, apply_modifiers=True, vgroups=True, scn=None):
144         '''
145         ob - the object that you want to get the mesh from
146         container_mesh - a Blender.Mesh type mesh that is reused to avoid a new datablock per call to getMeshFromObject
147         apply_modifiers - if enabled, subsurf bones etc. will be applied to the returned mesh. disable to get a copy of the mesh.
148         vgroup - For mesh objects only, apply the vgroup to the the copied mesh. (slower)
149         scn - Scene type. avoids getting the current scene each time getMeshFromObject is called.
150         
151         Returns Mesh or None
152         '''
153         
154         if not scn:
155                 scn= Blender.Scene.GetCurrent()
156         if not container_mesh:
157                 mesh = Blender.Mesh.New()       
158         else:
159                 mesh= container_mesh
160                 mesh.verts= None
161         
162         
163         type = ob.getType()
164         dataname = ob.getData(1)
165         tempob= None
166         if apply_modifiers or type != 'Mesh':
167                 try:
168                         mesh.getFromObject(ob.name)
169                 except:
170                         return None
171         
172         else:
173                 '''
174                 Dont apply modifiers, copy the mesh. 
175                 So we can transform the data. its easiest just to get a copy of the mesh. 
176                 '''
177                 tempob= Blender.Object.New('Mesh')
178                 tempob.shareFrom(ob)
179                 scn.link(tempob)
180                 mesh.getFromObject(tempob.name)
181                 scn.unlink(tempob)
182         
183         if type == 'Mesh':
184                 if vgroups:
185                         if tempob==None:
186                                 tempob= Blender.Object.New('Mesh')
187                         tempob.link(mesh)
188                         try:
189                                 # Copy the influences if possible.
190                                 groupNames, vWeightDict= meshWeight2Dict(tempMe)
191                                 dict2MeshWeight(mesh, groupNames, vWeightDict)
192                         except:
193                                 # if the modifier changes the vert count then it messes it up for us.
194                                 pass
195         
196         return mesh
197
198 type_tuple= type( (0,) )
199 type_list= type( [] )
200 def ngon(from_data, indices):
201         '''
202         takes a polyline of indices (fgon)
203         and returns a list of face indicie lists.
204         Designed to be used for importers that need indices for an fgon to create from existing verts.
205         
206         from_data is either a mesh, or a list/tuple of vectors.
207         '''
208         Mesh= Blender.Mesh
209         Window= Blender.Window
210         Scene= Blender.Scene
211         Object= Blender.Object
212         
213         if len(indices) < 4:
214                 return [indices]
215         temp_mesh_name= '~NGON_TEMP~'
216         is_editmode= Window.EditMode()
217         if is_editmode:
218                 Window.EditMode(0)
219         try:
220                 temp_mesh = Mesh.Get(temp_mesh_name)
221                 if temp_mesh.users!=0:
222                         temp_mesh = Mesh.New(temp_mesh_name)
223         except:
224                 temp_mesh = Mesh.New(temp_mesh_name)
225                 
226         if type(from_data) in (type_tuple, type_list):
227                 # From a list/tuple of vectors
228                 temp_mesh.verts.extend( [from_data[i] for i in indices] )
229                 temp_mesh.edges.extend( [(temp_mesh.verts[i], temp_mesh.verts[i-1]) for i in xrange(len(temp_mesh.verts))] )
230         else:
231                 # From a mesh
232                 temp_mesh.verts.extend( [from_data.verts[i].co for i in indices] )
233                 temp_mesh.edges.extend( [(temp_mesh.verts[i], temp_mesh.verts[i-1]) for i in xrange(len(temp_mesh.verts))] )
234         
235         
236         oldmode = Mesh.Mode()
237         Mesh.Mode(Mesh.SelectModes['VERTEX'])
238         for v in temp_mesh.verts:
239                 v.sel= 1
240         
241         # Must link to scene
242         scn= Scene.GetCurrent()
243         temp_ob= Object.New('Mesh')
244         temp_ob.link(temp_mesh)
245         scn.link(temp_ob)
246         temp_mesh.fill()
247         scn.unlink(temp_ob)
248         Mesh.Mode(oldmode)
249         
250         new_indices= [ [v.index for v in f.v]  for f in temp_mesh.faces ]
251         
252         if not new_indices: # JUST DO A FAN, Cant Scanfill
253                 print 'Warning Cannot scanfill!- Fallback on a triangle fan.'
254                 new_indices = [ [indices[0], indices[i-1], indices[i]] for i in xrange(2, len(indices)) ]
255         else:
256                 # Use real scanfill.
257                 # See if its flipped the wrong way.
258                 flip= None
259                 for fi in new_indices:
260                         if flip != None:
261                                 break
262                         for i, vi in enumerate(fi):
263                                 if vi==0 and fi[i-1]==1:
264                                         flip= False
265                                         break
266                                 elif vi==1 and fi[i-1]==0:
267                                         flip= True
268                                         break
269                 
270                 if not flip:
271                         for fi in new_indices:
272                                 fi.reverse()
273         
274         if is_editmode:
275                 Window.EditMode(1)
276                 
277         # Save some memory and forget about the verts.
278         # since we cant unlink the mesh.
279         temp_mesh.verts= None 
280         
281         return new_indices
282         
283
284
285 # EG
286 '''
287 scn= Scene.GetCurrent()
288 me = scn.getActiveObject().getData(mesh=1)
289 ind= [v.index for v in me.verts if v.sel] # Get indices
290
291 indices = ngon(me, ind) # fill the ngon.
292
293 # Extand the faces to show what the scanfill looked like.
294 print len(indices)
295 me.faces.extend([[me.verts[ii] for ii in i] for i in indices])
296 '''
297
298 def meshCalcNormals(me):
299         '''
300         takes a mesh and returns very high quality normals 1 normal per vertex.
301         The normals should be correct, indipendant of topology
302         '''
303         Ang= Blender.Mathutils.AngleBetweenVecs
304         Vector= Blender.Mathutils.Vector
305         SMALL_NUM=0.000001
306         # Weight the edge normals by total angle difference
307         # EDGE METHOD
308         
309         vertNormals= [ Vector() for v in xrange(len(me.verts)) ]
310         edges={}
311         for f in me.faces:
312                 for i in xrange(len(f.v)):
313                         i1, i2= f.v[i].index, f.v[i-1].index
314                         if i1<i2:
315                                 i1,i2= i2,i1
316                                 
317                         try:
318                                 edges[i1, i2].append(f.no)
319                         except:
320                                 edges[i1, i2]= [f.no]
321                                 
322         # Weight the edge normals by total angle difference
323         for fnos in edges.itervalues():
324                 
325                 len_fnos= len(fnos)
326                 if len_fnos>1:
327                         totAngDiff=0
328                         for j in reversed(xrange(len_fnos)):
329                                 for k in reversed(xrange(j)):
330                                         #print j,k
331                                         try:
332                                                 totAngDiff+= (Ang(fnos[j], fnos[k])) # /180 isnt needed, just to keeop the vert small.
333                                         except:
334                                                 pass # Zero length face
335                         
336                         # print totAngDiff
337                         if totAngDiff > SMALL_NUM:
338                                 '''
339                                 average_no= Vector()
340                                 for no in fnos:
341                                         average_no+=no
342                                 '''
343                                 average_no= reduce(lambda a,b: a+b, fnos, Vector())
344                                 fnos.append(average_no*totAngDiff) # average no * total angle diff
345                         #else:
346                         #       fnos[0]
347                 else:
348                         fnos.append(fnos[0])
349         
350         for ed, v in edges.iteritems():
351                 vertNormals[ed[0]]+= v[-1]
352                 vertNormals[ed[1]]+= v[-1]
353         for i, v in enumerate(vertNormals):
354                 me.verts[i].no= v
355
356
357
358
359 def pointInsideMesh(ob, pt):
360         Intersect = Blender.Mathutils.Intersect # 2 less dict lookups.
361         Vector = Blender.Mathutils.Vector
362         
363         def ptInFaceXYBounds(f, pt):
364                         
365                 co= f.v[0].co
366                 xmax= xmin= co.x
367                 ymax= ymin= co.y
368                 
369                 co= f.v[1].co
370                 xmax= max(xmax, co.x)
371                 xmin= min(xmin, co.x)
372                 ymax= max(ymax, co.y)
373                 ymin= min(ymin, co.y)
374                 
375                 co= f.v[2].co
376                 xmax= max(xmax, co.x)
377                 xmin= min(xmin, co.x)
378                 ymax= max(ymax, co.y)
379                 ymin= min(ymin, co.y)
380                 
381                 if len(f.v)==4: 
382                         co= f.v[3].co
383                         xmax= max(xmax, co.x)
384                         xmin= min(xmin, co.x)
385                         ymax= max(ymax, co.y)
386                         ymin= min(ymin, co.y)
387                 
388                 # Now we have the bounds, see if the point is in it.
389                 if\
390                 pt.x < xmin or\
391                 pt.y < ymin or\
392                 pt.x > xmax or\
393                 pt.y > ymax:
394                         return False # point is outside face bounds
395                 else:
396                         return True # point inside.
397                 #return xmax, ymax, xmin, ymin
398         
399         def faceIntersect(f):
400                 isect = Intersect(f.v[0].co, f.v[1].co, f.v[2].co, ray, obSpacePt, 1) # Clipped.
401                 if not isect and len(f.v) == 4:
402                         isect = Intersect(f.v[0].co, f.v[2].co, f.v[3].co, ray, obSpacePt, 1) # Clipped.
403                                 
404                 if isect and isect.z > obSpacePt.z: # This is so the ray only counts if its above the point. 
405                         return True
406                 else:
407                         return False
408         
409         
410         obImvMat = Blender.Mathutils.Matrix(ob.matrixWorld)
411         obImvMat.invert()
412         pt.resize4D()
413         obSpacePt = pt* obImvMat
414         pt.resize3D()
415         obSpacePt.resize3D()
416         ray = Vector(0,0,-1)
417         me= ob.getData(mesh=1)
418         
419         # Here we find the number on intersecting faces, return true if an odd number (inside), false (outside) if its true.
420         return len([None for f in me.faces if ptInFaceXYBounds(f, obSpacePt) if faceIntersect(f)]) % 2
421
422
423 # NMesh wrapper
424 Vector= Blender.Mathutils.Vector
425 class NMesh(object):
426         __slots__= 'verts', 'faces', 'edges', 'faceUV', 'materials', 'realmesh'
427         def __init__(self, mesh):
428                 '''
429                 This is an NMesh wrapper that
430                 mesh is an Mesh as returned by Blender.Mesh.New()
431                 This class wraps NMesh like access into Mesh
432                 
433                 Running NMesh.update() - with this wrapper,
434                 Will update the realmesh.
435                 '''
436                 self.verts= []
437                 self.faces= []
438                 self.edges= []
439                 self.faceUV= False
440                 self.materials= []
441                 self.realmesh= mesh
442         
443         def addFace(self, nmf):
444                 self.faces.append(nmf)
445         
446         def Face(self, v=[]):
447                 return NMFace(v)
448         def Vert(self, x,y,z):
449                 return NMVert(x,y,z)
450         
451         def hasFaceUV(self, flag):
452                 if flag:
453                         self.faceUV= True
454                 else:
455                         self.faceUV= False
456         
457         def addMaterial(self, mat):
458                 self.materials.append(mat)
459         
460         def update(self, recalc_normals=False): # recalc_normals is dummy
461                 mesh= self.realmesh
462                 mesh.verts= None # Clears the 
463                 
464                 # Add in any verts from faces we may have not added.
465                 for nmf in self.faces:
466                         for nmv in nmf.v:
467                                 if nmv.index==-1:
468                                         nmv.index= len(self.verts)
469                                         self.verts.append(nmv)
470                                         
471                 
472                 mesh.verts.extend([nmv.co for nmv in self.verts])
473                 for i, nmv in enumerate(self.verts):
474                         nmv.index= i
475                         mv= mesh.verts[i]
476                         mv.sel= nmv.sel
477                 
478                 good_faces= [nmf for nmf in self.faces if len(nmf.v) in (3,4)]
479                 #print len(good_faces), 'AAA'
480                 
481                 
482                 #mesh.faces.extend([nmf.v for nmf in self.faces])
483                 mesh.faces.extend([[mesh.verts[nmv.index] for nmv in nmf.v] for nmf in good_faces])
484                 if len(mesh.faces):
485                         if self.faceUV:
486                                 mesh.faceUV= 1
487                         
488                         #for i, nmf in enumerate(self.faces):
489                         for i, nmf in enumerate(good_faces):
490                                 mf= mesh.faces[i]
491                                 if self.faceUV:
492                                         if len(nmf.uv) == len(mf.v):
493                                                 mf.uv= [Vector(uv[0], uv[1]) for uv in nmf.uv]
494                                         if len(nmf.col) == len(mf.v):
495                                                 for c, i in enumerate(mf.col):
496                                                         c.r, c.g, c.b= nmf.col[i].r, nmf.col[i].g, nmf.col[i].b
497                                         if nmf.image:
498                                                 mf.image= nmf.image
499                 
500                 mesh.materials= self.materials[:16]
501
502 class NMVert(object):
503         __slots__= 'co', 'index', 'no', 'sel', 'uvco'
504         def __init__(self, x,y,z):
505                 self.co= Vector(x,y,z)
506                 self.index= None # set on appending.
507                 self.no= Vector(0,0,1) # dummy
508                 self.sel= 0
509                 self.uvco= None
510 class NMFace(object):
511         __slots__= 'col', 'flag', 'hide', 'image', 'mat', 'materialIndex', 'mode', 'normal',\
512         'sel', 'smooth', 'transp', 'uv', 'v'
513         
514         def __init__(self, v=[]):
515                 self.col= []
516                 self.flag= 0
517                 self.hide= 0
518                 self.image= None
519                 self.mat= 0 # materialIndex needs support too.
520                 self.mode= 0
521                 self.normal= Vector(0,0,1)
522                 self.uv= []
523                 self.sel= 0
524                 self.smooth= 0
525                 self.transp= 0
526                 self.uv= []
527                 self.v= [] # a list of nmverts.
528         
529 class NMCol(object):
530         __slots__ = 'r', 'g', 'b', 'a'
531         def __init__(self):
532                 self.r= 255
533                 self.g= 255
534                 self.b= 255
535                 self.a= 255